Compare commits

..

428 Commits

Author SHA1 Message Date
Yukihiro "Matz" Matsumoto 735b94535c numeric.c: raise TypeError for non-Integer bitwise operands
Integer#&, #| and #^ read the right-hand operand with mrb_integer()
without checking its type. For a Float (or any non-Integer) this reads an
unrelated union field and returns a garbage value instead of raising, as
CRuby does. Raise TypeError via mrb_int_noconv() when the operand is not
an Integer. Bigint operands are still handled before this point, and the
shift operators keep coercing their width as before.

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-05 11:23:43 +09:00
Yukihiro "Matz" Matsumoto 9982bc1223 random.c: fix signed integer overflow in rand() with integer ranges
rand_range_int() computed `end - begin + 1` in mrb_int before checking
for a reversed or empty range. For extreme bounds the subtraction itself
overflowed mrb_int (UndefinedBehaviorSanitizer signed-integer-overflow),
and the wraparound could turn a reversed range into a positive span,
defeating the guard.

Reject reversed or empty ranges before subtracting, and compute the
candidate count in unsigned arithmetic, so no signed overflow is
possible. A new unsigned uniform sampler draws from the full mrb_int
domain, so valid ranges whose width exceeds MRB_INT_MAX now return a
uniform in-range value instead of nil, matching CRuby.

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-05 11:23:43 +09:00
Yukihiro "Matz" Matsumoto 24f428af5e Merge pull request #6879 from Asmod4n/method_alias
Add alias support for mruby-method and mruby-proc-ext
2026-06-05 07:43:35 +09:00
Asmod4n a6168fa111 Merge branch 'method_alias' of https://github.com/Asmod4n/mruby into method_alias 2026-06-04 17:37:12 +02:00
Asmod4n d8911416c1 add alias support to mruby-method and mruby-prox-ext 2026-06-04 17:36:41 +02:00
Yukihiro "Matz" Matsumoto b0e7118e72 Merge pull request #6878 from mruby/dependabot/bundler/bundler-dependencies-6264482842
build(deps): bump yard from 0.9.43 to 0.9.44 in the bundler-dependencies group
2026-06-03 10:46:57 +09:00
dependabot[bot] 4073ad386c build(deps): bump yard in the bundler-dependencies group
Bumps the bundler-dependencies group with 1 update: [yard](https://yardoc.org).


Updates `yard` from 0.9.43 to 0.9.44

---
updated-dependencies:
- dependency-name: yard
  dependency-version: 0.9.44
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 20:17:23 +00:00
Yukihiro "Matz" Matsumoto c58b0b66ba Merge pull request #6877 from dearblue/array-ext
Free the index array immediately at the end of `ary_combination_next()`
2026-06-02 07:07:56 +09:00
dearblue 6704a385b7 Free the index array immediately at the end of ary_combination_next() 2026-06-01 22:27:59 +09:00
Yukihiro "Matz" Matsumoto 72e5ebed55 AUTHORS: update entries [ci skip] 2026-06-01 14:15:29 +09:00
Yukihiro "Matz" Matsumoto 9d084b09b7 mruby-io: write literal strings directly via fd_write_buf
io_puts_str, io_puts_ary, and io_puts allocated a fresh mruby String
for every "\n", empty-array marker, "[...]" overflow marker, and
no-arg newline, only for fd_write to unpack it back to ptr/len.
The allocations also stayed on the GC arena across the recursive
walk, scaling pressure with array length.

Split fd_write into fd_write_buf (the EINTR-resilient write loop
over ptr/len) plus the existing mrb_value wrapper, and add a
FD_WRITE_LIT macro for compile-time-known literals. Replace the
four mrb_str_new_lit + fd_write pairs with FD_WRITE_LIT. The "" s
"" inside the macro enforces that the argument is a string literal
so sizeof(s) - 1 is the correct length.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-31 09:09:20 +09:00
Yukihiro "Matz" Matsumoto f5ca906852 mruby-compiler: fix bare nil? in if/unless to use self as receiver
The if/unless nil? optimization called codegen() on the call node's
receiver, but a bare `nil?` is parsed as an FCALL whose receiver is
NULL. codegen(NULL) emits OP_LOADNIL, so the JMPNIL was testing the
literal nil instead of self, making `if nil?` always behave as
`if nil.nil?` (always true) and `unless nil?` always skip its body.
Load self when the receiver is implicit. Fixes #6874.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-30 07:39:45 +09:00
Yukihiro "Matz" Matsumoto 543bc325f3 Merge pull request #6875 from mruby/dependabot/github_actions/github-actions-dependencies-754f0868f5
build(deps): bump github/codeql-action from 4.35.5 to 4.36.0 in the github-actions-dependencies group
2026-05-30 07:29:35 +09:00
dependabot[bot] 828efa9b87 build(deps): bump github/codeql-action
Bumps the github-actions-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 4.35.5 to 4.36.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/9e0d7b8d25671d64c341c19c0152d693099fb5ba...7211b7c8077ea37d8641b6271f6a365a22a5fbfa)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 17:56:25 +00:00
Yukihiro "Matz" Matsumoto 94bd329afe Merge pull request #6871 from hasumikin/fix/execute_task
Fix execute_task() so unhandled task exceptions become task results
2026-05-29 07:05:36 +09:00
Yukihiro "Matz" Matsumoto be36b67a12 mruby-task: clear dead stack slots when marking preempted tasks
mrb_task_mark_all marked a task's live registers but, unlike
mark_context_stack in gc.c, never cleared the slots above the live
range. When a preempted task's live range later shrank (a frame had
returned), the stale object pointers left in those slots were neither
marked nor cleared: the objects were swept while the pointers survived.
Re-entering the same frame reused those slots, and the next mark of the
resumed task hit a freed object, tripping the MRB_TT_FREE assertion in
mrb_gc_mark.

Clear the dead slots after marking, exactly as mark_context_stack does
for the running context.

Fixes #6870.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-29 05:59:14 +09:00
HASUMI Hitoshi a502be4f9f Fix fallback when abnormal error happens
It is not likely happens but if happened, in pattern 1, the whole
process abort when mrb->jmp is NULL.
Instead, make the status MRB_TASK_STOPPED and delegate following
logic of "Handle task termination"
2026-05-29 01:30:24 +09:00
HASUMI Hitoshi f1232334c0 Fix execute_task() so unhandled task exceptions become task results
This patch fixes a bug that c09196c introduced.

## Background

`mrb_task_run()` has two usage patterns:

1. Called directly from `main()` as the top-level scheduler (PicoRuby and R2P2). There is no surrounding C exception handler, so mrb->jmp is NULL on entry
2. Called from Ruby code via Task.run, bootstrapped on top of mruby's regular call chain. mrb->jmp is non-NULL

Historically, an unhandled exception raised inside a task body was turned into the task's result value by `mrb_vm_exec()`: the L_RAISE path walked callinfo down to cibase, ran `fiber_terminate()`, and - because c->vmexec was TRUE and prev_jmp was NULL in pattern 1 - took `return mrb_obj_value(mrb->exc)`.
That value landed in t->result and could be read back through `mrb_task_value()` / `join()`.

## What c09196c broke

It consider only pattern 2 and wrapped `mrb_task_run()` in a protect frame (MRB_TRY / mrb_protect_error) to guarantee that loop_running is cleared on exception.
As a side effect, mrb->jmp is now always non-NULL while a task body is executing, so the L_RAISE path takes `MRB_THROW(prev_jmp)` instead of returning the exception value.

In pattern 2 this merely changed the semantics (exceptions started propagating out of `Task.run` instead of being stored as task results).
In pattern 1 it was FATAL: the throw unwound to mrb_task_run's catch handler, which called `mrb_exc_raise()` to re-propagate, and with no outer jmpbuf this aborted the process.
PicoRuby/R2P2 could no longer retrieve task exceptions via `mrb_task_value()`.

## Fix

Restore the "task exception becomes task result" contract uniformly for both patterns, independent of mrb->jmp:

* Add `mrb_task_state.exception_as_result`. When set, `mrb_vm_exec()`'s non-root_c L_RAISE branch returns the exception as a value even if prev_jmp is non-NULL, instead of throwing

* `execute_task_vm()` raises the flag around `mrb_vm_exec()`, captures the exception into `t->result`, and clears `mrb->exc`

* Wrap `execute_task_vm()` in `mrb_protect_error()` as a safety net for rare paths that still unwind via MRB_THROW (e.g. CINFO_SKIP frames). exception_as_result is reset both at the end of the body and immediately after `mrb_protect_error()` returns, so a caught throw does not leave the llag set

* Expose `Task#value` to retrieve t->result from Ruby, since Task#join cannot deliver the value through its return path under cooperative scheduling

* Add a test asserting that `Task#join` on a task that raised returns the exception object, matching the pre-c09196c observable behavior

## Notes

The "task exception becomes task result" semantics match the mruby/c's rrt0.c and the spirit of CRuby's Thread (an unhandled exception in a thread does not kill the scheduler / process; it surfaces when the thread is joined).
The visible API shape still differs from CRuby - `Task#join` here returns the exception object rather than re-raising it - but the scheduler is no longer destabilized by task errors in either invocation pattern.
2026-05-29 01:25:47 +09:00
Yukihiro "Matz" Matsumoto dad7c0fa36 Merge pull request #6869 from hasumikin/fix/task
Separate the union of timeslice and result in struct mrb_task
2026-05-28 20:20:43 +09:00
HASUMI Hitoshi e0d6d39ce9 Write validate flag (status) after setting timeslice
`mrb_tick()` observes `status == RUNNING` before touching `timeslice`,
so initializing `timeslice` first avoids exposing a partially initialized running state
2026-05-28 19:18:17 +09:00
HASUMI Hitoshi 564726c44f t->result now can be marked unconditionally 2026-05-28 19:16:04 +09:00
HASUMI Hitoshi 5e669560eb Amend field position to pack effectively 2026-05-28 19:07:20 +09:00
HASUMI Hitoshi 030a08092a Separate the union of timeslice and result in struct mrb_task
Bug scenario:
* VM returns an Exception `t->state.result = mrb_vm_exec(...);`
* Despite task is still MRB_TASK_STATUS_RUNNING, IRQ triggered by chance and `mrb_tick()` executes `t->state.timeslice--;`
* But the same memory area already holds the `result`, `timeslice--` reduces `result.value.p`'s top byte

The fix is to separate `timeslice` and `result` into different fields.
I have considered improving critical sections, but I ended up with this patch because I believe it is widely effective and less error-prone.
2026-05-28 18:48:13 +09:00
Yukihiro "Matz" Matsumoto d2a1c43a5f vm.c: defer task switch across C call boundary
A task switch (early return from mrb_vm_exec) is unsafe when execution
has re-entered the VM from C (mrb_funcall, mrb_yield, mrb_vm_run). The
C stack frame between the scheduler's mrb_vm_exec and the current frame
cannot be suspended, and returning early from the inner mrb_vm_exec
leaves the call-info stack drifted, so the enclosing mrb_vm_run trips
its `c->ci == c->cibase || ...` assertion (or corrupts state in a
non-debug build).

This happens when a block yielded from a C function wakes a task, for
example Task::Queue#push from inside a block passed to a C method via
mrb_yield_argv.

Defer the switch via task_across_c_boundary, which walks the call-info
stack for a C frame (cci > 0), mirroring the cooperative guard in
Task.pass. The switch resumes once execution unwinds back to a frame
with no C boundary. The gc.iterating short-circuit from #6862 is kept
as a cheap pre-check.

Fixes #6868.
Refs #6864.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-28 15:36:21 +09:00
Yukihiro "Matz" Matsumoto dc671f007a vm.c: restore mrb->jmp on early return for task switch
mrb_vm_exec sets mrb->jmp to its own stack-local c_jmp on entry and
restores the caller's prev_jmp on every normal return path. The early
return added for task switching (RETURN_IF_TASK_STOPPED) skipped that
restore, so after a task was preempted via Task.pass the dangling
mrb->jmp pointed into mrb_vm_exec's freed stack frame. A subsequent
raise then longjmp'd into that freed frame and crashed.

Restore prev_jmp in the early-return path, matching the normal returns.

  Task.new { Task.pass }
  Task.pass
  raise "boom"          # SIGSEGV before this change

Fixes #6863.
Refs #6864.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-28 13:19:10 +09:00
Yukihiro "Matz" Matsumoto c09196ca36 mruby-task: switch mrb_task_run to mrb_protect_error
mruby/throw.h is documented as a core-internal header that should not
be included from mrbgems or user code, and under MRB_USE_CXX_EXCEPTION
or MRB_USE_CXX_ABI the MRB_TRY/MRB_CATCH macros expand to C++
exception syntax that does not compile in a C source file. The
wrapping added in #6866 (commit ee82a7fcc6) accidentally tripped that
constraint.

Drop the throw.h include and use mrb_protect_error() from
mruby/error.h instead. The helper takes a body function plus
userdata, runs it under its own jmpbuf, and reports whether an
exception was caught. We re-raise via mrb_exc_raise so the visible
behavior matches the previous code: loop_running is cleared on both
success and exception, and an exception propagates back out.

Refs #6866.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-28 12:10:08 +09:00
Yukihiro "Matz" Matsumoto dfc542dbc5 mruby-task: terminate suspended task in suspend test
The "Task#suspend doesn't raise" test left its task parked in
q_suspended_. A later test ("Task.run inside Task.run is a noop")
calls Task.run, and the scheduler will not exit its loop while any
task sits in the suspended (or waiting) queue, so the whole run
hung.

Terminate the task at the end of the suspend test so it does not
leak into the shared scheduler state the next test depends on. This
fixes the hang at its source rather than scrubbing leaked tasks from
the consumer side.

Refs #6866, #6867.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-28 12:02:07 +09:00
Yukihiro "Matz" Matsumoto 20d8e202a2 Merge pull request #6866 from 0x1eef/task_oneloop
task: return `nil` when given a nested call to `Task.run`
2026-05-28 11:28:07 +09:00
0x1eef 4bdde3a0a8 task: add test 2026-05-27 23:27:51 -03:00
0x1eef ee82a7fcc6 fix: wrap mrb_task_run in MRB_TRY/MRB_CATCH 2026-05-27 23:19:43 -03:00
0x1eef 819156e678 task: return nil when given a nested call to Task.run
When you try to start an event loop inside an event loop,
the mruby process will SIGSEGV:

```ruby
Task.new { Task.run }
Task.run
```

This change turns the second call to `Task.run` into a noop
that returns nil instead.

Fix #6865
2026-05-27 23:10:09 -03:00
Yukihiro "Matz" Matsumoto d1289ac8f9 vm.c: defer task switches during gc.iterating
RETURN_IF_TASK_STOPPED used to bail out of mrb_vm_exec as soon as
task.switching was set, even when the exec was called re-entrantly
from inside a heap-walk callback (e.g. ObjectSpace.each_object via
mrb_yield). Bailing in that situation only unwinds the inner exec,
leaving the outer C iteration to keep calling the callback. The
call-info stack drifts on each subsequent mrb_yield, and the program
either trips the cibase assertion in mrb_vm_run or crashes in
__longjmp.

Hold off the switch while mrb->gc.iterating is set so the heap walk
finishes intact; the switch then fires at the next OP boundary once
the walk releases the flag. MRB_TASK_STOPPED is intentionally not
deferred, since exiting promptly is still correct when the task
itself is going away.

Fixes #6862.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-28 07:27:37 +09:00
Yukihiro "Matz" Matsumoto f5a5bfcbf6 limitations.md: replace binding note with general mrbgem pattern
The Kernel#binding entry was treating "feature provided by mrbgem"
as a per-method limitation, which does not scale. mruby implements
much of Ruby's standard surface area through mrbgems, so listing
each one would grow without bound.

Replace it with a single top-level note explaining the general
pattern: which features are available depends on the linked gems,
and a NoMethodError on a familiar Ruby method usually points to a
missing gem rather than a true mruby gap.

Refs #6861.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-27 08:49:07 +09:00
Yukihiro "Matz" Matsumoto 012691279d mruby-string-ext: skip scrub UTF-8 assertions on non-UTF-8 builds
When MRB_UTF8_STRING is undefined, String#scrub is a no-op that
returns the receiver as-is (the #else branch added in ccb62ceb57).
The unit tests assumed UTF-8 semantics unconditionally, so a build
of just mruby-string-ext without UTF-8 strings failed all five scrub
tests.

Guard the UTF-8-dependent assertions with `skip unless "あ".length
== 1`, and add a paired test that asserts the no-op behaviour on the
non-UTF-8 build (skipped on UTF-8 builds).

Verified against the build config from the report:
  - UTF-8 (host-debug): 5 tests pass, 1 skip
  - non-UTF-8 (noutf8):  5 skip, 1 test pass

Closes #6860.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-26 23:10:45 +09:00
Yukihiro "Matz" Matsumoto ccb62ceb57 mruby-string-ext: add String#scrub
Replaces each maximal run of invalid UTF-8 bytes with a replacement
string (U+FFFD by default), returning a valid UTF-8 copy. Mirrors
CRuby's String#scrub (Feature #6752) -- the recovery counterpart to
the existing String#valid_encoding? detection API.

Validation matches utf8code() in src/string.c after the RFC 3629 /
Unicode D93b conformance fixup (#2708): overlong encodings, UTF-16
surrogates, and codepoints above U+10FFFF are all treated as invalid.
This is stricter than the existing mrb_utf8len()-based check used by
valid_encoding?, so a string can report valid_encoding? = true and
still get scrubbed; aligning valid_encoding? is a follow-up.

The block form lives in mrblib on top of two C primitives -- __scrub
and __scrub_chunks -- to avoid VM re-entry from C per CLAUDE.md.
Non-String block return values are coerced via to_s (CRuby raises
TypeError instead; the choice is locked in by test).

Closes #6859.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-25 23:36:42 +09:00
Yukihiro "Matz" Matsumoto 4f398f6126 mruby-task: prefix queue helpers with mrb_task_
`q_insert_task` and `q_delete_task` were exporting bare `q_*` names
from libmruby.a -- single-letter prefixes don't belong to the gem's
namespace and risk colliding with anything else linked in.

Rename to `mrb_task_q_insert` / `mrb_task_q_delete`, matching the
`mrb_task_*` convention already used for the rest of the gem's
externally visible symbols. Callers in task.c and task_queue.c are
updated to the new names.

Closes #6858.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-25 06:48:13 +09:00
Yukihiro "Matz" Matsumoto 19c857a773 mruby-regexp: prefix exposed engine entry points with mrb_re_
The Pike VM and pattern compiler were exporting bare names like
`re_compile`, `re_exec`, `re_free`, `re_is_word_char`, `re_utf8_charlen`,
`re_utf8_decode`. `re_exec` in particular collides with the obsolete
BSD libc function of the same name (still present on FreeBSD/NetBSD
base), so embedding mruby alongside platform regex could surface a
link-time symbol clash.

Rename all six entry points to `mrb_re_*` to keep the gem's external
symbols inside mruby's namespace. Source file names and the public
header path are unchanged.

Refs #6858.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-25 06:09:11 +09:00
Yukihiro "Matz" Matsumoto f6564c7db1 class.c: make define_method_m static
The helper is used only inside class.c (mrb_mod_define_method_m,
mod_define_method, define_singleton_method) -- no need to expose it
through libmruby.a. Drops one of the non-`mrb_*` linkage symbols
flagged in #6858.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-25 06:06:05 +09:00
Yukihiro "Matz" Matsumoto 36dd9eab88 vformat.rb: fix format/arg type mismatch in %!d test case
The test pushed mrb_int via vf.i but the format read int via %!d. On
x86_64/aarch64 the va_arg slots overlap so reading the low 32 bits of
the pushed int64 returned the right value, but on strict-alignment
ABIs (MIPS o32) va_arg(ap, int) reads the alignment padding and the
format prints 0 instead of the value.

Make the format match the helper: vf.i pairs with %!i (reads mrb_int),
matching the surrounding lines 44-50 convention and the "inspect
mrb_int" label.

Reported by vobloeb in #6857.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-24 10:31:14 +09:00
Yukihiro "Matz" Matsumoto b1adab4e87 Merge pull request #6856 from vobloeb/patch-1
test/bintest.rb: tokenize ENV['EMULATOR'] via Shellwords.split
2026-05-23 12:16:39 +09:00
Yukihiro "Matz" Matsumoto d21eceb286 mruby-regexp: disable first-byte skip when pattern can match empty
first_set_walk returned TRUE when it reached RE_MATCH via epsilon
transitions, but that's exactly the case where the optimization is
wrong: an empty-matchable pattern can start matching at any position,
including bytes that aren't in the computed first-byte set. The
skip-ahead loop in pike_vm then advanced past valid empty-match
positions, producing a match at the wrong offset (e.g. /a?/.match("b")
reported the empty match at index 1 instead of 0).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-23 09:58:31 +09:00
Yukihiro "Matz" Matsumoto b9b8186f00 mruby-regexp: don't bump jump offsets that already point at insertion site
insert_inst was incrementing every offset >= pos, but an offset equal to
pos already points to the new instruction's slot -- bumping it shifts
the target onto whatever code got displaced (typically the body of the
quantified atom). For patterns like /a?b?/ the SPLIT for `a?` then
landed on `CHAR 'b'` instead of the new SPLIT for `b?`, so the "skip a"
thread tried to consume 'b' and died, and both atoms failed to match
zero characters at once.

Fixes #6853.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-23 09:58:30 +09:00
Yukihiro "Matz" Matsumoto cddaec7264 mruby-compiler: bail out of array-literal pattern match opt on splat
The optimization that skips `deconstruct` and the size check when the
case/in value is an array literal trusted node count, ignoring splat.
An element like `*a` expands at runtime, so [*a] was treated as length
1 and matched only patterns of that length.

Fixes #6854.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-23 09:58:30 +09:00
Yukihiro "Matz" Matsumoto 3aea2f7e67 Merge pull request #6855 from mruby/dependabot/github_actions/github-actions-dependencies-1f5b201d0b
build(deps): bump the github-actions-dependencies group with 2 updates
2026-05-23 07:58:14 +09:00
vobloeb c0d8abd0a3 test/bintest.rb: tokenize ENV['EMULATOR'] via Shellwords.split
`Command::CrossTestRunner#emulator` returns a shell-quoted string, which `Build#run_test` un-quotes via `sh`, but `CrossBuild#run_bintest` propagates verbatim through `ENV['EMULATOR']` to `test/bintest.rb`. The latter splices it into an Open3 exec-mode argv, where the literal `"` survives into `execve(2)` and the kernel returns `ENOENT`.

Switching to `Shellwords.split(ENV['EMULATOR'])` round-trips the quoted string correctly and also fixes multi-token emulator commands (e.g. `qemu-aarch64 -L /sysroot`), which currently end up concatenated into `argv[0]`.

Verified against mruby `3.3.0`, `3.4.0`, `4.0.0`, and `master`. Cross-build of mruby for `aarch64-unknown-linux-musl` (qemu-user 8.2.10): bintests went from 17/75 crashing → 100/100 passing.
2026-05-22 17:01:38 +00:00
dependabot[bot] 309b99efa5 build(deps): bump the github-actions-dependencies group with 2 updates
Bumps the github-actions-dependencies group with 2 updates: [github/codeql-action](https://github.com/github/codeql-action) and [j178/prek-action](https://github.com/j178/prek-action).


Updates `github/codeql-action` from 4.35.4 to 4.35.5
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/68bde559dea0fdcac2102bfdf6230c5f70eb485e...9e0d7b8d25671d64c341c19c0152d693099fb5ba)

Updates `j178/prek-action` from 2.0.3 to 2.0.4
- [Release notes](https://github.com/j178/prek-action/releases)
- [Commits](https://github.com/j178/prek-action/compare/6ad80277337ad479fe43bd70701c3f7f8aa74db3...bdca6f102f98e2b4c7029491a53dfd366469e33d)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-dependencies
- dependency-name: j178/prek-action
  dependency-version: 2.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-22 14:54:27 +00:00
Yukihiro "Matz" Matsumoto 66f438d8fe mruby-bin-debugger: return on OOM in mrb_debug_set_break_method
mrb_debug_set_break_method() freed set_class after mrdb_strdup() of
method_name failed but did not return. Execution continued into
alloc_breakpoint(), which on failure double-freed set_class, or on
success stored the dangling pointer in the breakpoint table for later
use-after-free. Return MRB_DEBUG_NOBUF immediately after the free.

mrdb_strdup uses mrb_malloc_simple which returns NULL on OOM (it does
not raise), so the NULL check is reachable in practice.

close #6851

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-22 07:08:26 +09:00
Yukihiro "Matz" Matsumoto 4507b4a633 mruby-bigint: gate mpz_mod's Barrett path on the algorithm's precondition
Barrett reduction requires x < 2^(2*bits(m)); the gate condition only
required x->sz >= y->sz + 2, which let x.sz reach 25 limbs against a
4-limb modulus. When the precondition is violated, mpz_barrett_reduce
silently truncates high limbs and returns garbage.

Integer#remainder, which routes through mpz_mod, was affected:
(3**500).remainder((2**100)+3) returned the wrong value. Integer#%
took the udiv path via mpz_mmod and was unaffected.

Add x->sz <= 2 * y->sz to the gate so out-of-range inputs fall
through to the general udiv path.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-22 07:08:26 +09:00
Yukihiro "Matz" Matsumoto d30ddd4296 Merge pull request #6852 from mruby/dependabot/pre_commit/pre-commit-hooks-613cf0bdf4
build(deps): bump https://github.com/rubocop/rubocop from v1.86.1 to 1.86.2 in the pre-commit-hooks group
2026-05-22 07:03:27 +09:00
dependabot[bot] a4e12dfc42 build(deps): bump https://github.com/rubocop/rubocop
Bumps the pre-commit-hooks group with 1 update: [https://github.com/rubocop/rubocop](https://github.com/rubocop/rubocop).


Updates `https://github.com/rubocop/rubocop` from v1.86.1 to 1.86.2
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.86.1...v1.86.2)

---
updated-dependencies:
- dependency-name: https://github.com/rubocop/rubocop
  dependency-version: 1.86.2
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 16:28:35 +00:00
Yukihiro "Matz" Matsumoto 206b9f7477 mruby-bigint: pre-reduce base in mpz_powm_montgomery to satisfy REDC
REDC's input T must satisfy T < R*N; mpz_powm_montgomery() computed
T = base * R^2 without first reducing base modulo n. When base >= n,
T exceeds R*N and REDC silently truncates upper limbs, producing a
wrong base_mont and ultimately a wrong result.

Use mpz_mmod (general division path) to pre-reduce base mod n before
the multiplication by R^2.

Visible effect: (2**160).pow(2, (2**40)+1) returned 0 instead of 1.
A separate pre-existing issue in mpz_mod's Barrett path (broken
precondition check) means that path could return a wrong reduction
for large operands; mmod sidesteps that path entirely.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-21 17:07:51 +09:00
Yukihiro "Matz" Matsumoto 6ac2c3dc56 mruby-bigint: maintain canonical sn=0 when trim reduces sz to 0
mpz_t treats sn (sign) as the canonical "is zero" flag (zero_p(x) :=
(x)->sn == 0). When an arithmetic operation produces a value whose
limbs trim to zero size, sn must be reset to 0 to preserve the
invariant. Several call sites already enforced this locally (e.g.
mpz_sub line 616); make trim() responsible so every caller benefits.

Without this, an inconsistent zero bignum (sn!=0, sz=0) can flow into
mpz_sqr, miss the zero_p guard, and reach mpz_init_heap with hint=0
where mpn_zero(NULL, 0) invokes UB (memset() declares its first
argument nonnull). The trip survives at runtime on glibc but is
formally undefined behavior, flagged by UBSan via Integer#pow(b,e,m)
with specific operands.

close #6849

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-21 17:04:08 +09:00
Yukihiro "Matz" Matsumoto 72c117cb9e variable.c: guard assign_class_name against unresolvable symbol
mrb_sym_name_len() returns NULL when sym is 0, out of range, or
references a freed symbol slot. assign_class_name() indexed [0]
without checking, so malformed bytecode whose OP_CLASS operand
indexed past irep->slen could feed a bogus sym here and crash on
NULL[0]. Skip the class-naming side effect when the sym does not
resolve to a name.

close #6842

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-21 16:11:48 +09:00
Yukihiro "Matz" Matsumoto 465e634d48 mruby-regexp: fix SEGV on uninitialized Regexp's hash and ==
regexp_init() called re_compile() before setting @source / @flags
IVs, so a Regexp that survived a compile-time exception (e.g. picked
up via ObjectSpace.each_object after `Regexp.new("(")` raised) was
left with no @source. obj.hash then dereferenced nil through
mrb_str_hash() and crashed.

Set the IVs before re_compile(), and make regexp_hash / regexp_eql
defensive against a non-String @source so Regexp.allocate.hash also
behaves.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-21 12:32:40 +09:00
Yukihiro "Matz" Matsumoto 9a1c1eabf9 Merge pull request #6841 from dearblue/gc
Small refactor for `incremental_sweep_phase()`
2026-05-21 10:09:55 +09:00
Yukihiro "Matz" Matsumoto 54c8427df2 mruby-regexp: handle \b inside character class as backspace
Inside `[...]`, `\b` denotes U+0008 (backspace) -- the same as
MRI/Onigmo and PCRE.  parse_escape() was missing the case, so
the backslash was dropped and the bare letter `b` was inserted
into the class.  `[\b]` therefore matched every `b` instead of
backspace.

Add `case 'b': return '\b';` to parse_escape().  The function
is only reached from the character-class body and range
endpoints; the top-level dispatcher emits RE_WBOUND for `\b`
before falling through, so the word-boundary semantics outside
`[...]` are unchanged.

Reported by Sam Ruby in matz/spinel#632; same engine bug
affects both spinel and mruby.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-21 08:23:15 +09:00
dearblue df38160d82 Omit the check whether the typetag is MRB_TT_FREE after calling obj_free()
With commit cbc3dbedb4, freed objects will always be `MRB_TT_FREE`.
2026-05-20 21:15:54 +09:00
dearblue 2c0bccab66 Use else to skip the while loop in incremental_sweep_phase()
As a result, the variables can also be localized.
2026-05-20 21:15:54 +09:00
Yukihiro "Matz" Matsumoto 17d124b00d array.c (mrb_ary_splice): re-modify a after self-aset ary_dup
`a[range] = a` on a long-enough array tripped a heap-buffer-overflow
in value_move(). mrb_ary_splice's self-aset branch calls ary_dup(a)
to get an independent copy of the source elements, but ary_dup ->
ary_replace converts the source to shared as a copy-on-write
optimization when the length exceeds ARY_REPLACE_SHARED_MIN. After
that, a->as.heap.aux is reinterpreted as `shared` (the union member)
and ARY_CAPA(a) reads from the shared pointer's bits rather than
the real capacity. The expand-capa check below then silently mis-
sizes and value_move walks past the buffer.

Re-modify `a` immediately after ary_dup to un-share before the in-
place mutation. The buffer reads through `argv` (which now points
into the dup's storage) stay valid because ary_modify on a multi-
reference shared array allocates a fresh buffer for `a` and leaves
the original buffer owned by the dup.

Found via clusterfuzz mruby_fuzzer testcase 6525563811725312;
regression test covers a[3, 2] = a on a 31-element array (above
the ARY_REPLACE_SHARED_MIN=20 threshold).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 14:27:22 +09:00
Yukihiro "Matz" Matsumoto 3e2ce88eab hw-spi: wrap splat-bearing method signatures as code in README
After f91936b06e kept the splat notation unescaped, prettier
kept flagging the standalone `*` in the SPI#write / SPI#transfer
headings every CI run.  Wrap the two signatures in backticks so
prettier treats them as inline code (which they are), and the
`*data` form stays unescaped without further conflict.  Same
convention is already used in mruby-kernel-ext/README.md
(e.g. `### \`fail(*args)\``).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-20 08:35:22 +09:00
Yukihiro "Matz" Matsumoto 7378be58cb Merge pull request #6838 from dearblue/gc_stat
Use `MRB_SYM()` in `gc_stat()`
2026-05-19 22:44:41 +09:00
Yukihiro "Matz" Matsumoto ebe4a7aed0 Merge pull request #6837 from dearblue/mrb_const_cache_clear
Avoid surrounding `#if` when using `mrb_const_cache_clear()`
2026-05-19 22:44:37 +09:00
dearblue 6d12f1f331 Use MRB_SYM() in gc_stat() 2026-05-19 22:28:15 +09:00
dearblue 6daa33c9a8 Avoid surrounding #if when using mrb_const_cache_clear()
`mrb_const_cache_clear()` is always available.
2026-05-19 21:22:27 +09:00
Yukihiro "Matz" Matsumoto ae29ab7db9 numeric.c: bounds-check float in Float#div before mrb_int cast
`Float#div(Integer)` cast its receiver to mrb_int unconditionally,
which is undefined behavior when the float is outside the
representable mrb_int range.  ASan reports the UB on inputs like
`5e+56.div(1)`.

Reported by ClusterFuzz testcase
clusterfuzz-testcase-minimized-mruby_fuzzer-5137605569347584.

Guard the cast with FIXABLE_FLOAT and route over-range receivers
to mrb_bint_div when MRB_USE_BIGINT is defined (matching
flo_rounding_int's pattern), or raise via mrb_int_overflow when
not.  Existing `(float in range).div(int)` semantics are
unchanged.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 12:15:38 +09:00
Yukihiro "Matz" Matsumoto 73255d3b70 mruby-numeric-ext: avoid signed overflow on Integer#gcd/lcm with MRB_INT_MIN
Negating MRB_INT_MIN (-2^63) is signed overflow (UB) because
2^63 does not fit in mrb_int.  Both `mrb_int_gcd` and `int_lcm`
took the absolute value via `if (x < 0) x = -x`, which trips on
MRB_INT_MIN.

Reported by ClusterFuzz testcase
clusterfuzz-testcase-minimized-mruby_fuzzer-5137605569347584.

* mrb_int_gcd: cast each input to mrb_uint before negating; the
  Euclidean reduction runs in unsigned.  The cast back at the
  end yields MRB_INT_MIN only when the mathematical gcd is 2^63
  (i.e., gcd(MIN, 0) or gcd(MIN, MIN)).
* int_gcd: detect the negative return value from mrb_int_gcd
  and raise via mrb_int_overflow, since the true result does
  not fit.
* int_lcm: short-circuit raise when either operand is
  MRB_INT_MIN (after the existing zero check), since the abs
  would overflow and the lcm with any non-zero operand could
  not fit anyway.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 12:05:33 +09:00
Yukihiro "Matz" Matsumoto db2845aae0 mruby-regexp: bounds-check group index in RE_BACKREF
The RE_BACKREF execution path read `captures[group * 2]` and
`captures[group * 2 + 1]` without verifying that the group
index fit in the allocated captures array. A pattern like
`/\1/` (no capture group, but a backreference to group 1) is
accepted by the compiler and lands in execution with `ncap = 2`
(only group 0 slots) and an instruction asking for group 1 --
a 4-byte read past the end of the allocation.

Reported by ClusterFuzz testcase
clusterfuzz-testcase-minimized-mruby_fuzzer-5474946829844480.

Add `if (group * 2 + 1 >= ncap) return FALSE;` ahead of the
captures access, mirroring the bounds guard already present in
RE_SAVE. The compiler's permissive `\<digit>` handling stays
unchanged; the runtime now treats a reference to a non-existent
group as a non-match rather than UB.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 10:40:45 +09:00
Yukihiro "Matz" Matsumoto 5bb4a15086 mruby-regexp: cap bt_match recursion depth
The backtracking engine recurses via C function calls at RE_SPLIT,
RE_SPLITNG, RE_SAVE, RE_LOOKAHEAD, RE_NEG_LOOKAHEAD, RE_LOOKBEHIND,
and RE_NEG_LOOKBEHIND.  Patterns like `(?=)+` make the engine
recurse without consuming input, exhausting the C stack and
triggering SIGSEGV long before MRB_REGEXP_STEP_LIMIT is reached
(each recursion charges only ~1 step, but each frame costs ~150
bytes of stack).

Reported by ClusterFuzz testcase
clusterfuzz-testcase-minimized-mruby_fuzzer-4653331195953152.

Add an integer recursion-depth counter passed alongside the step
counter, and abort the current branch with FALSE when it exceeds
MRB_REGEXP_RECURSION_LIMIT (default 1000, configurable like
STEP_LIMIT).  Legitimate patterns nest only a few levels;
pathological inputs bail without crashing the VM.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 10:34:39 +09:00
Yukihiro "Matz" Matsumoto 3e676af568 Merge pull request #6836 from hasumikin/fix/task
Fix mruby-task: keep join waiter wakeup under one IRQ critical section
2026-05-18 16:26:31 +09:00
HASUMI Hitoshi c4d205496d Fix mruby-task: keep join waiter wakeup under one IRQ critical section 2026-05-18 16:11:04 +09:00
Yukihiro "Matz" Matsumoto 1317a8bf5d Merge pull request #6835 from hasumikin/task-queue
Introduce Task::Queue
2026-05-18 15:38:16 +09:00
HASUMI Hitoshi a1a77e5acc Call task_check_scheduler_lock in queue_pop_try to avoid dead lock 2026-05-18 15:30:44 +09:00
HASUMI Hitoshi c9ae2524ee Cache Task::Error in task_error_class_ iniailized in mrb_init_task_queue() 2026-05-18 15:26:38 +09:00
HASUMI Hitoshi 458078f10e Introduce Task::Queue
- This patch implements `Task::Queue` mirroring `Thread::Queue` in CRuby.
- Producer/Consumer pattern is now possible with no polling

## No Top-level pollution
- No top-level `Queue` defined
- No `TaskError`. Instead, `Task::Error` happens when `Task::Queue#pop(true)` when empty
- No `ClosedQueueError`. `Task::Error` also happens when pushing to closed queue

## Future Work
- `Task::SizedQueue`
2026-05-18 14:23:42 +09:00
Yukihiro "Matz" Matsumoto d1bffb902e Merge pull request #6834 from Asmod4n/glib_hal
mruby-task GLib HAL
2026-05-17 23:31:33 +09:00
Asmod4n 17858cc5bd mruby-task GLib HAL
Lets mruby-task embed cleanly in any GLib-based event loop -- GTK,
libsoup, GStreamer, or anything else built on GMainContext. Tasks
become regular GSources, so the scheduler runs alongside whatever
else is on the loop without polling or busy-waiting.

Sleeping tasks cost zero CPU: the HAL parks until the next wakeup
deadline rather than ticking on a fixed cadence. Multiple mrb_states
on the same thread share one dispatcher and one ticker. Preemption,
Task.run, sleeper wakes, and foreign-loop integration all use the
same primitives, so embedders can mix Task.run with g_main_loop_run
freely.

ref mruby#6825
2026-05-17 13:44:20 +02:00
Yukihiro "Matz" Matsumoto aa25be69d7 Merge pull request #6833 from Asmod4n/patch-4
Improve wakeup tick condition check
2026-05-17 19:02:42 +09:00
Yukihiro "Matz" Matsumoto c8dd299ca8 mrbgems.md: document Platform Ports and External HAL Providers
Add a section on the `ports/<name>/` mechanism (`conf.ports`
selection, fallback chain) and on the external HAL provider
naming convention (`hal-<short>-<conf>` overrides the bundled
ports/* of the gem whose name ends in `-<short>`).

Also add `ports/<name>/` to the GEM structure tree.

ref #6825

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-17 18:48:23 +09:00
Yukihiro "Matz" Matsumoto e7f5387f5d gem.rb: detect external HAL by hal-<short>-* naming
67f137e201 introduced spec.hal_pattern on the target gem to
declare its expected HAL provider names.  But the decision to
override is naturally an opt-in choice belonging to the external
HAL gem (or build_config), not the target.  Drop hal_pattern and
hardcode the naming rule in resolve_external_hal!: a gem named
`hal-<short>-<conf>` overrides the gem whose last `-`-separated
segment is <short>.  No spec attribute on the target gem; no flag
on add_dependency; the convention does all the work.

The pre-be6413f0d8 convention was `hal-<platform>-<gem>` (e.g.,
hal-posix-task) for platform sort across gems.  Now that the
common platforms live under ports/, sorting by which target gem
a HAL is for is more useful, so the order is reversed:
`hal-<gem>-<conf>` (e.g., hal-task-glib).

ref #6825

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-17 18:38:38 +09:00
Yukihiro "Matz" Matsumoto 67f137e201 gem.rb: hal_pattern for external HAL provider override
A gem can declare

  spec.hal_pattern = /\Ahal-.*-task\z/

to indicate that another gem whose name matches the pattern (and
which depends on this gem for headers) replaces the built-in
ports/<conf.ports>/ HAL implementation.  After all gems are set
up, List#resolve_external_hal! drops the target's ports/* objs
from its object list so the matching gem supplies the HAL
symbols.  Two or more matches is reported as a build error.

This restores the pre-be6413f0d8 ability to maintain an
out-of-tree HAL via add_dependency + naming convention, without
reintroducing the "HAL information scattered across gems"
problem: the parent gem still owns the scheduler, headers, and
bundled posix/win ports; external HAL gems are an explicit,
opt-in override.

Declare /\Ahal-.*-task\z/ for mruby-task -- the same naming
pattern used before be6413f0d8.

ref #6825

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-17 17:33:26 +09:00
Hendrik 8b63ddbfd1 Refactor task wakeup logic and add comments
Enhance wakeup logic for sleeping tasks to handle UINT32_MAX sentinel case and prevent race conditions with mrb_tick.

This enables tickless Task hals to be build.
2026-05-17 08:14:58 +02:00
Hendrik 2668619143 Improve wakeup tick condition check
We got a off by one error here, checking against UINT32_MAX doesn't silently set a wrong value.

When running the task mgem with a busy loop this doesn't cause an issue, but with a tickless timer only one timer ever gets fired and then the task mgem stops working.
2026-05-17 07:31:08 +02:00
Yukihiro "Matz" Matsumoto f22a991065 build: pick first matching ports/<name>/ per gem
When `conf.ports` was given multiple names, every matching ports/
directory for each gem was compiled -- fine for single-name builds
and for gems that have at most one matching port, but a footgun
once HAL-using gems start providing multiple alternative ports
(e.g. mruby-task with both posix and an alternative runloop).

Iterate the chain and break on the first match.  Existing builds
that pass at most one name per gem keep the same behavior;
`conf.ports :rp2040, :posix` now reads as "use rp2040 if a gem has
that port, otherwise posix" rather than "compile both".

ref #6825

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-17 06:57:54 +09:00
Yukihiro "Matz" Matsumoto 40264c9aad mruby-compiler: accept double-quoted strings in case/in patterns
The p_value rule only accepted bare tSTRING tokens, which the lexer
emits for single-quoted strings.  Double-quoted strings emit
tSTRING_BEG ... tSTRING (or with interpolation, tSTRING_BEG
string_rep tSTRING), so

  case "hello"
  in "hello"
    :match
  end

raised "syntax error, unexpected string literal" at the `"` after
`in`.  Use the existing `string` non-terminal instead of bare
tSTRING, which also enables alternation (`"a" | "b"`), interpolation
(`"hel#{x}"`), and concatenation by juxtaposition in patterns.

close #6830

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-16 22:01:52 +09:00
Yukihiro "Matz" Matsumoto b059f3df25 mruby-bin-mrbc: skip multi-input debug-info bintest when bin-mruby absent
The bintest added in 0d87198c92 runs the compiled output via the
mruby binary and uses Kernel#puts in the script body.  In builds
that omit mruby-bin-mruby, the test failed with "sh: bin/mruby:
not found" before reaching the actual assertion.

Skip the assert block when bin/mruby isn't built, and switch the
script's puts to print since Kernel#puts only exists when mruby-io
is loaded (same pattern as #6814).

close #6831

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-16 21:17:41 +09:00
Yukihiro "Matz" Matsumoto e5c2479ee6 mruby-bin-mruby: skip regexp split bintest when mruby-regexp absent
The bintest added in 9d8d41006b uses `"hello world".split(/\s+/)`,
which raises `uninitialized constant Regexp` in builds that omit
mruby-regexp. Probe whether the mruby binary defines Regexp and
skip the whole assert block when it doesn't; the test is regression
coverage for mruby-regexp's String#split override, so when regexp
isn't loaded there is nothing to verify.

close #6832

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-16 20:23:23 +09:00
Yukihiro "Matz" Matsumoto 087da64f99 Merge pull request #6829 from Asmod4n/master
add IO#autoclose= and IO#autoclose?
2026-05-14 17:04:36 +09:00
Hendrik 9eb8865a8a Fix documentation for autoclose? method 2026-05-14 09:57:26 +02:00
Asmod4n aea2bbcbe5 add IO#autoclose= and IO#autoclose? 2026-05-14 09:53:18 +02:00
Yukihiro "Matz" Matsumoto 3e5e84e391 mruby-task: update README and headers for ports-based HAL layout
The HAL was integrated into mruby-task/ports/{posix,win}/ in
be6413f0d8 and the function names were updated in 610ff67906, but
the docs and one header still described the old separate-gem layout:

  - mrbgems/mruby-task/README.md described hal-posix-task and
    hal-win-task as separate gems, used the pre-rename function
    names (mrb_task_hal_*), and omitted mrb_hal_task_sleep_us.
    Rewrote the HAL section to match the current ports/ model.

  - mrbgems/mruby-task/include/task.h had three orphan declarations
    (mrb_task_hal_init / _final / _idle_cpu) from before the
    rename. Removed; the real declarations are in task_hal.h.

  - mrbgems/mruby-task/include/task_hal.h had a comment referring
    to the removed hal-* gems.

  - doc/guides/amalgamation.md listed hal-posix-io and hal-posix-task
    as platform-specific gems alongside mruby-io and mruby-task; both
    are now ports under the parent gem.

Reported by Asmod4n in #6825.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-14 13:48:55 +09:00
Yukihiro "Matz" Matsumoto d1608ba72e mruby.h: add MRB_API to mrb_const_cache_clear
The function is the symmetric counterpart of mrb_method_cache_clear
declared two lines above, but was added without MRB_API in
50bc8c6136. Both are VM-internal cache invalidators exposed in the
public header under the same #ifndef MRB_NO_*_CACHE pattern; make
their decoration consistent.

Reported by dearblue in #6826.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-14 13:23:05 +09:00
Yukihiro "Matz" Matsumoto 6d2d31ac5f mruby.h: use MRB_INLINE for mrb_funcall_argv1 / mrb_funcall_argv2
These public convenience wrappers were declared with raw
`static inline`, while every other public inline helper in mruby.h
uses MRB_INLINE. MRB_INLINE expands to static inline, so this is a
spelling-only change for grep-consistency across the C API.

Reported by dearblue in #6827.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-14 13:12:26 +09:00
Yukihiro "Matz" Matsumoto 9e93337479 mruby-regexp: copy named-capture names into owned arena
re_compile stored raw pointers into the pattern source in
pat->named_captures[i].name. Two ways this could dangle:

  - With /x, the source was c.stripped, freed at end of compile.
    Later reads (regexp construction, MatchData[:name] lookup) hit
    freed memory.

  - Without /x, the pointer aliased the input string's RSTRING_PTR.
    Mutating that string after Regexp.new could re-buffer it, leaving
    name dangling.

Allocate one arena buffer per regexp (only when num_named > 0) and
copy all names in. Common-case regexps without named captures pay
zero bytes.

Reported by OSS-Fuzz (testcase 5695283416858624).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-13 12:34:50 +09:00
Yukihiro "Matz" Matsumoto 403b75fbeb fp_uscale.c: clamp parser underflow guard to POW10_MIN
mrb_read_float's underflow short-circuit used `final_p < -342 - nd`,
which for nd > 1 can be below POW10_MIN (-343). That let
parse_decimal call prescale() with a final_p below POW10_MIN, causing
an out-of-bounds read of pow10_tab. Tighten the guard to
`final_p < POW10_MIN`; values below that threshold cannot be
represented as a non-zero double for any mantissa within the parser's
19-digit cap.

Reported by OSS-Fuzz (testcase 6097379597287424).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-13 12:02:30 +09:00
Yukihiro "Matz" Matsumoto 27e14c16c4 limitations.md: document Class#initialize re-invocation difference
mruby's Class#initialize accepts re-invocation through __send__
silently, while CRuby raises TypeError. The superclass argument is
ignored on re-invocation, so no destructive side effect is possible.
Reported on Twitter by cacao_soft.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-13 12:02:30 +09:00
Yukihiro "Matz" Matsumoto 82de0b0ee5 Merge pull request #6824 from mruby/dependabot/github_actions/github-actions-dependencies-2cbc4c3382
build(deps): bump actions/labeler from 6.0.1 to 6.1.0 in the github-actions-dependencies group
2026-05-13 07:40:09 +09:00
dependabot[bot] 0eaae2824b build(deps): bump actions/labeler
Bumps the github-actions-dependencies group with 1 update: [actions/labeler](https://github.com/actions/labeler).


Updates `actions/labeler` from 6.0.1 to 6.1.0
- [Release notes](https://github.com/actions/labeler/releases)
- [Commits](https://github.com/actions/labeler/compare/634933edcd8ababfe52f92936142cc22ac488b1b...f27b608878404679385c85cfa523b85ccb86e213)

---
updated-dependencies:
- dependency-name: actions/labeler
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-12 20:37:41 +00:00
Yukihiro "Matz" Matsumoto 478ada3bf4 fp_uscale.c: clamp precision in %g to avoid OOB in fixed_width
When sprintf is called with a precision larger than the double's
significand width (e.g. "%.51g"), fixed_width() indexed pow10 tables
out of bounds and produced a negative shift exponent. Cap the
internal digit count to 18 in the %g branch, matching the existing
%e and %f branches; downstream loops already zero-pad to the
caller's precision so visible output is unchanged.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12 11:19:20 +09:00
Yukihiro "Matz" Matsumoto 16151a0daa proc.c: mark Proc#dup / Proc#clone copies as orphan blocks
A copied Proc now always carries `MRB_PROC_ORPHAN`, so calling a
`dup`'d block that contains `break` or `return` raises
`LocalJumpError` even while the original yielding method is still on
the stack.

This is stricter than CRuby — which only marks the copy orphan once
the original yielding method returns — but matches mruby's
memory-first design: tracking the original via a back pointer in
RProc would also enlarge the GC mark set. dearblue's option (1) in
the linked issue, accepted for the simpler RProc layout.

Document the divergence in `doc/limitations.md` and add a regression
test in `test/t/proc.rb`.

close #6345

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 15:06:24 +09:00
Yukihiro "Matz" Matsumoto 322642364a capi.md: document that dfree handlers must not re-enter the VM
The `dfree` callback registered via `mrb_data_type.dfree` runs from
inside GC sweep, so allocating Ruby objects, calling `mrb_funcall` /
`mrb_yield`, raising exceptions, or otherwise re-entering the VM
can trigger a recursive GC that revisits the same object and causes
double-free (see #6316). Make the rule explicit in the "Wrapping C
Structures" section.

close #6316

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 14:39:01 +09:00
Yukihiro "Matz" Matsumoto 3fc7dd1858 mruby-proc-ext: Proc#curry should check max arity for lambdas
`lambda {|a, b=nil|}.curry(3)` used to silently return a curried Proc;
CRuby raises `ArgumentError` because the lambda accepts at most 2 args.
Use `self.parameters` to compute the upper bound (count `:req`/`:opt`
entries, unbounded if `:rest`/`:keyrest` is present) and add the
corresponding range check alongside the existing minimum check.

close #2855

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 14:06:28 +09:00
Yukihiro "Matz" Matsumoto 46151db893 Merge pull request #6823 from jbampton/pin-all-actions
Pin remaining actions to hash
2026-05-11 13:07:12 +09:00
Yukihiro "Matz" Matsumoto 9d8d41006b mruby-regexp: alias original String#split before overriding it
`class String; def split` in mruby-regexp/mrblib/string_regexp.rb
replaced the C-defined String#split rather than overriding it, so
the in-Ruby `return super if pattern.nil?` paths raised
NoMethodError for any `"x".split(...)` call once mruby-regexp was
loaded (the default full-core production binary).

The regression wasn't caught by the test suite because per-gem
tests run under mrb_open_core() with only the gem's dep_list, so
the broken override is never visible from test/t/string.rb
(mruby-test, not mruby-regexp).

Add `alias __split split` at the top of the override class body,
which captures the C-defined method, and change the Ruby override
to delegate via `__split(pattern, limit)` for non-regexp fallback
paths. Add a bintest under mruby-bin-mruby that runs through
bin/mruby (full gem load) to catch this regression class.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 12:45:43 +09:00
John Bampton 788ad33333 Pin remaining actions to hash 2026-05-11 13:21:28 +10:00
Yukihiro "Matz" Matsumoto 0a78ab986d Merge pull request #6822 from jbampton/remove-trailing-whitespace
Run prek; remove trailing whitespace; cleanup markdown
2026-05-11 10:56:12 +09:00
Yukihiro "Matz" Matsumoto f91936b06e hw-spi: keep splat parameter notation unescaped in README
The previous commit's prettier pass escaped `*data` to `\*data` in
the SPI#write / SPI#transfer method signatures. The escape is
defensive (prettier avoids any standalone `*` to dodge italic
misparses) but unnecessary in these positions: GFM only treats `*`
as italic when paired with a closing `*`, which never happens in a
method signature. The unescaped form reads more naturally to Ruby
users.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 10:56:00 +09:00
Yukihiro "Matz" Matsumoto 0d87198c92 mruby-compiler: preserve line number across multi-file boundary
When mrbc compiles multiple input files (e.g. `mrbc -g -o out.mrb
a.rb b.rb`), the bison parser's one-token lookahead can buffer the
final token of one file before partial_hook switches to the next.
By the time bison reduces that token into an AST node,
`mrb_parser_set_filename` has already reset `p->lineno` to 0, so
init_var_header recorded lineno=0 for the previous file's last
statement and codegen propagated the previous instruction's line.

Save the lineno into `prev_file_lineno` immediately before the
reset so init_var_header can restore the correct value when it
detects the lookahead edge case (lineno==0 && filename_index>0).

close #1316

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 10:38:02 +09:00
Yukihiro "Matz" Matsumoto 3dbf5f6e27 build/command.rb: route mrbc output through tempfile to avoid pipe race
The previous IO.popen capture of mrbc stdout (`-o-`) is vulnerable
to a Windows MinGW race: with parallel rake (-m), stdout pipe
inheritance can allow unrelated `_pp` build-progress messages from
sibling worker threads to leak into the captured pipe content,
corrupting the generated C file (recently seen as `CC build/...`
text inside gem_test.c, causing compile errors at unrelated lines).

Switch to `mrbc -o <tmpfile>` so the output is materialised in a
file before merging into the destination, fully isolating mrbc
from the parent's STDOUT.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 09:52:35 +09:00
Yukihiro "Matz" Matsumoto ad0ea8571f fp_uscale.c: zero-initialize digs to silence MinGW gcc warning
MinGW gcc emits -Wmaybe-uninitialized for `digs[0]` in the
`mrb_format_float` function because it cannot prove that
`count_digits()` always returns >= 1. The code is correct
(count_digits's `d == 0` early return makes the lower bound
clear to a human reader), but gcc's flow analysis does not
follow through. Initialize `digs` to silence the false positive
across all MinGW gcc CI configurations.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 08:47:05 +09:00
Yukihiro "Matz" Matsumoto 9bb40386ce limitations.md: document nested def scope in singleton methods
mruby places `def` written inside `def self.foo` on the receiver's
singleton class (making it a class method of the enclosing class).
CRuby places it as an instance method of the lexical enclosing
class. This is a long-standing divergence that we've chosen to
document rather than change.

close #1536

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 08:31:19 +09:00
Yukihiro "Matz" Matsumoto 89e81e9130 limitations.md: document absence of implicit type conversion
Add a section explaining that mruby intentionally does not consult
to_int/to_str/to_ary/to_hash for implicit type coercion in built-in
operations, even though identity versions remain defined on the
corresponding built-in types. Note that Float#to_int and Array#to_ary
are not defined, and contrast with explicit conversion methods
(to_i, to_s, to_a) which do work.

ref #2979

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 08:13:51 +09:00
Yukihiro "Matz" Matsumoto fe17d66363 mruby-string-ext: reject surrogates and ill-formed UTF-8
Make `Integer#chr("UTF-8")` reject UTF-16 surrogate code points
(U+D800..U+DFFF), and make `String#ord` reject ill-formed UTF-8 byte
sequences (overlong encodings, surrogates encoded as UTF-8, and code
points above U+10FFFF), matching CRuby and RFC 3629.

The `utf8code()` helper now decodes the code point first and then
validates the range per byte length:
  len=2: cp >= 0x80                 (rejects overlong)
  len=3: cp >= 0x800 and not D800..DFFF
  len=4: 0x10000 <= cp <= 0x10FFFF

close #2708

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 07:48:23 +09:00
John Bampton bda1d2d5a5 Run prek; remove trailing whitespace; cleanup markdown 2026-05-10 14:56:53 +10:00
Yukihiro "Matz" Matsumoto db29955b7a mruby-internal: switch internal mrb_funcall_id callers to argv1/argv2
Replace the 12 in-tree mrb_funcall_id call sites (all argc=1 or
argc=2) with the typed inline helpers added in the previous commit.
After inlining each call site allocates exactly the slots it needs
on its own frame instead of going through mrb_funcall_id's fixed
16-element argv buffer.

ref #5804

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-10 01:32:33 +09:00
Yukihiro "Matz" Matsumoto 575609f3b9 mruby.h: add mrb_funcall_argv1/argv2 inline helpers
mrb_funcall_id() always reserves a 16-element argv buffer on its
stack frame regardless of the actual argument count. The two new
static inline wrappers allocate exactly one or two argument slots,
saving 100-220 bytes of stack per call after inlining (the savings
depend on mrb_value size under the active boxing configuration).

close #5804

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-10 01:30:29 +09:00
Yukihiro "Matz" Matsumoto b4f6450509 mruby-compiler: do not flip + and - opcode for negative literal
The peephole in gen_addsub() rewrote `q + -n` into OP_SUBI n (and
`q - -n` into OP_ADDI n) by negating n. For numeric receivers this is
equivalent, but for receivers overriding + or - the runtime fallback
dispatches the flipped method, losing the original operator. Restrict
the fold to non-negative immediates; negative falls through to the
normal OP_ADD/OP_SUB path with LOADI of the literal value.

close #2557
ref #2579

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-09 19:47:27 +09:00
dearblue 2e429a034d gem.rb: add post_user_config hook for gem-author finalization
Lets a gem author register a block that runs after the user's
`build.gem` block has been processed.  Intended for filling in
defaults that depend on user-supplied configuration — for example,
library auto-detection that should only kick in when the user
hasn't explicitly chosen which library to use.

Initialization order is now:
  1. block in MRuby::Gem::Specification.new (gem author defaults)
  2. block in build.gem (user override)
  3. block in post_user_config (gem author finalize, new)

Closes #6212, picked from PR by dearblue with the method renamed
from last_initializer to post_user_config (per matz's review).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-09 15:10:58 +09:00
Yukihiro "Matz" Matsumoto 81cfeb917e Merge pull request #2479 from take-cheeze/sym_slice
Implement `Symbol#slice` and `Symbol#[]` in mruby-symbol-ext.
2026-05-09 10:32:20 +09:00
Yukihiro "Matz" Matsumoto 43c0b50cbd mruby-socket: implement Socket.ip_address_list
Returns an Array of Addrinfo objects for all local IP addresses
(IPv4 and IPv6) on every network interface, matching CRuby's API.

Backed by getifaddrs(3) on POSIX and GetAdaptersAddresses on
Windows (requires iphlpapi.lib, added to the Windows linker libs).
The HAL returns binary sockaddr strings; src/socket.c wraps each
into Addrinfo so the wrapping code stays platform-agnostic.

Resolves the second error reported in #5659 (after the IO.select
fix from the HAL split): "undefined method 'ip_address_list' for
Class". Issue #5659 itself is closed; this lands the missing API.

Ref #5659.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-09 10:22:19 +09:00
Yukihiro "Matz" Matsumoto 7d0d6fd413 Merge pull request #6821 from mruby/dependabot/github_actions/github-actions-dependencies-937d73b4db
build(deps): bump github/codeql-action from 4.35.2 to 4.35.3 in the github-actions-dependencies group
2026-05-09 08:09:06 +09:00
dependabot[bot] 8703a49fb4 build(deps): bump github/codeql-action
Bumps the github-actions-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 4.35.2 to 4.35.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4.35.2...v4.35.3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-08 14:54:16 +00:00
Yukihiro "Matz" Matsumoto c0fb9a190c host-shared.rb: produce libmruby.so for distro packaging
Builds the shared library FROM the existing static archive via
-Wl,--whole-archive, leaving the static-build pipeline (and the
test infrastructure that depends on it) untouched.

Produces in build/host/lib/:
  libmruby.so              SONAME=libmruby.so.<MAJOR>.<MINOR>
  libmruby.so.<MAJOR>.<MINOR>  symlink for runtime DT_NEEDED resolution
  libmruby.map             linker version script: MRUBY_<RELEASE_NO>
  libmruby_core.so + symlink (similarly)

Symbol versioning ties to MRUBY_RELEASE_NO (e.g. MRUBY_40000 for
4.0.0). mruby has historically had ABI breaks at TEENY granularity,
so the version tag uses the full release number rather than just
MAJOR.MINOR.

Closes #6239.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-08 20:48:58 +09:00
Yukihiro "Matz" Matsumoto e95c0fc683 Merge pull request #6691 from jbampton/add-pre-commit-hook
pre-commit: add hook to ensure Makefiles are indented with tabs
2026-05-08 19:31:54 +09:00
Yukihiro "Matz" Matsumoto 274af06d06 Merge pull request #6596 from jbampton/add-hook-check-useless-excludes
pre-commit add official meta hook `check-useless-excludes`
2026-05-08 19:31:51 +09:00
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
dearblue cf20687ee7 mruby-eval: unify f_instance_eval and f_class_eval
Their bodies were nearly identical: same argument parsing,
same proc creation, same target-class plumbing. The only
differences are which method to delegate to in the block-given
case (mrb_obj_instance_eval vs. mrb_mod_module_eval) and
which class to use as the target (singleton vs. self-as-class).

Extract the shared logic into object_eval(self, class_eval).
The two top-level dispatchers become one-line wrappers.

Closes #6579, picked from PR by dearblue.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-08 11:51:25 +09:00
Yukihiro "Matz" Matsumoto 817ec1bd90 Merge pull request #6580 from dearblue/File.basename
Improve `File.basename`
2026-05-08 11:41:36 +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
Yukihiro "Matz" Matsumoto 3f709f61dd Merge pull request #6820 from petekinnecom/inheritedOrdering
Fix Class inherited hook ordering
2026-05-08 11:00:48 +09:00
Yukihiro "Matz" Matsumoto bf0915dd03 Merge pull request #6817 from dearblue/array-ext
Integrate `Array#{permutation,combination}` into `Array#__combination`
2026-05-08 10:56:42 +09:00
dearblue df74d57857 array.h: introduce ARY_GETMEM/RARRAY_GETMEM and type ARY_EMBED_PTR
Adds two API helpers that mirror CRuby's RSTRING_GETMEM idiom:

- ARY_GETMEM(a, ptr, len): inside-mruby helper that takes a
  struct RArray* and assigns ptr/len from the embed or heap form.
- RARRAY_GETMEM(a, ptr, len): public wrapper on an mrb_value.

Both expand to a single ARY_EMBED_P check, with a uniqued local so
the array argument is evaluated only once (callers can safely pass
expressions with side effects).

Also type the ARY_NO_EMBED stub of ARY_EMBED_PTR as
((mrb_value*)NULL) instead of integer 0, so it composes cleanly in
pointer expressions like the new ARY_GETMEM.

The build-error issue this originated from (compilation with
MRB_ARY_NO_EMBED) was already fixed differently in master via
#ifndef guards (commit 78658d67e). These additions stand on their
own as new API for downstream gems.

Closes #6712, picked from PR by dearblue.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-08 10:47:58 +09:00
Yukihiro "Matz" Matsumoto 91b60802db mruby-regexp: cap character class count and free named_captures
Two related bugs uncovered by OSS-Fuzz testcase 6692915710853120:

1. add_class allowed unbounded growth of c->classes. Class IDs are
   stored in re_inst.a (uint8_t), so any ID >= 256 silently aliases
   another class via the cast at emit sites. Worse, c->class_capa
   (uint16_t) overflows on doubling past 32768 -> 0, then
   mrb_realloc(..., 0) returns NULL, and the next memset(&c->classes[id])
   segfaults at NULL+offset. Cap with RE_MAX_CLASSES = 256 (the encoding
   limit) and raise via compile_error past that.

2. Once the crash is fixed, the testcase exposes a leak of
   c->named_captures: compile_error frees c->code, c->classes, and
   c->stripped (commit 3f321f09bc) but missed named_captures. Add it
   to the same cleanup block.

Reported by OSS-Fuzz (clusterfuzz testcase 6692915710853120).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-08 10:32:03 +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 28624ecfd8 mruby-regexp: cap {n}/{n,m} quantifiers to prevent overflow
parse_quantifier read digits via min = min * 10 + d with no upper
bound, allowing patterns like /a{1111558833}/ to overflow int and
trigger signed-integer-overflow UB. Even without UB, the value
flows into compile_quantified's emit loop where it would attempt
to emit a billion copies of the atom.

Add RE_MAX_REPEAT = 32768 (the largest value that still fits in
re_inst.offset, the uint16_t jump field) and reject quantifiers
beyond that during parsing via compile_error. Apply the same cap
to the max field.

Reported by OSS-Fuzz (clusterfuzz testcase 6152367367323648).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-07 16:25:12 +09:00
Yukihiro "Matz" Matsumoto f267925d45 mruby-socket: translate Winsock errors to errno on Windows
Winsock APIs (socket, bind, connect, accept, recv, send, ...) report
errors via WSAGetLastError() and do not set errno, so mrb_sys_fail
on Windows was reading a stale or zero errno. Result: every socket
failure raised SystemCallError with errno 0 ("Success") instead of
the appropriate Errno::* class.

Add mrb_hal_socket_set_errno_from_last_error() to the HAL:
- POSIX: no-op (failed calls already set errno)
- Windows: maps WSAGetLastError() to a POSIX errno via wsa_to_errno()
  with 32 cases covering the common Winsock error codes; unmapped
  codes fall back to EIO. Each case is #ifdef-guarded against older
  MSVC CRTs that lack a particular Exxx.

In src/socket.c, route the 22 socket-API failure sites through a new
sock_sys_fail() helper that calls the HAL translator before
mrb_sys_fail. Also fix mrb_hal_socket_set_nonblock() on Windows,
which was returning -1 without setting errno after ioctlsocket
failure.

POSIX behavior unchanged (verified: TCPSocket connect refused ->
Errno::ECONNREFUSED, bind to privileged port -> Errno::EACCES, bad
sockopt -> Errno::EOPNOTSUPP).

Closes #6819, reported by Asmod4n.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-07 11:01:46 +09:00
Yukihiro "Matz" Matsumoto ada9ca7236 Merge pull request #6818 from mruby/dependabot/github_actions/github-actions-dependencies-7e7af3d814
build(deps): bump j178/prek-action from 2.0.2 to 2.0.3 in the github-actions-dependencies group
2026-05-07 08:54:57 +09:00
dependabot[bot] 428c9b5b03 build(deps): bump j178/prek-action
Bumps the github-actions-dependencies group with 1 update: [j178/prek-action](https://github.com/j178/prek-action).


Updates `j178/prek-action` from 2.0.2 to 2.0.3
- [Release notes](https://github.com/j178/prek-action/releases)
- [Commits](https://github.com/j178/prek-action/compare/cbc2f23eb5539cf20d82d1aabd0d0ecbcc56f4e3...6ad80277337ad479fe43bd70701c3f7f8aa74db3)

---
updated-dependencies:
- dependency-name: j178/prek-action
  dependency-version: 2.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-06 15:03:04 +00:00
dearblue 3db11cef09 Integrate Array#{permutation,combination} into Array#__combination
Compared to a pure Ruby implementation, this results in faster performance, eliminates recursive calls, and removes the creation of intermediate objects.

The "permutation" implementation in `ary_combination_next()` is slow for C.
However, it does not require a heap other than the index array.
2026-05-06 18:27:16 +09:00
dearblue e7a375d4be Preparations for integrating the implementation of Array#{permutation,combination}
- Modify the `mrb_combination_state` structure to accommodate feature extensions
  - Rename `Array#__repeated_combination` to `__combination`
  - Consolidate integer checks for arguments into `__combination`
  - Since checking for integer types using both `__to_int` and `0 <=>` is redundant, use only `__to_int`
  - Since `__combination` now accepts symbols instead of booleans, the call to `to_enum` has also been consolidated
2026-05-06 18:27:16 +09: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 3cc60d31c6 error.h, array.h: support MRB_NAN_BOXING on 32-bit
On 32-bit platforms whose ABI gives 8-byte members 8-byte alignment
(xtensa, ARM, MIPS, PowerPC, ...), MRB_NAN_BOXING failed to build with
"RVALUE size must be within 5 words" because two structs got padded
past the budget:

- struct RBreak: had an existing MRB_USE_RBREAK_VALUE_UNION workaround
  that stores the value as uint32_t[] to avoid forcing 8-byte alignment
  on the struct, but the gate only enabled it for MRB_NO_BOXING.
  Extend to NAN_BOXING + 32-bit, with a NAN_BOXING-specific get/set
  (no separate tt to stash since nan-boxing encodes type in the bits).

- struct RArray: MRB_ARY_NO_EMBED was similarly gated to NO_BOXING;
  embedded mrb_value[] forces 8-byte alignment of the inner union and
  pads the heap-form layout. Extend the gate to NAN_BOXING + 32-bit.

Both gates now name the structural property (32-bit + mrb_value has an
8-byte aligned member) rather than enumerating boxing modes, so adding
new boxing modes won't silently miss this class of bug again.

i386's System V ABI gives uint64_t only 4-byte alignment, hiding the
problem on x86 -m32; -malign-double simulates the strict-alignment ABI
that exhibits the failure, and is what was used to verify the fix.

Closes #6815, reported by dearblue.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-06 07:56:25 +09:00
Yukihiro "Matz" Matsumoto 90ead4bdaf Merge pull request #6816 from dearblue/array-ext
Rename the members of the `mrb_combination_state` structure
2026-05-05 23:24:08 +09:00
dearblue a8c841433f Rename the members of the mrb_combination_state structure
Since these are expressed as "nPk" or "nCk" in mathematics, rename `n` to `k` and `array_size` to `n`.
Additionally, rename the parameters `#__repeated_combination` and `#__combination_init` from `n` to `k`.

However, the parameter `n` in `#repeated_permutation` and `#repeated_combination` remains unchanged to align with CRuby.
2026-05-05 22:25:26 +09:00
Yukihiro "Matz" Matsumoto 3f321f09bc mruby-regexp: fix leak and UAF on compile error paths
compile_error is the chokepoint for all regex-compile errors;
mrb_raisef longjmps out of re_compile, abandoning the stack-local
re_compiler struct. Three connected bugs:

1. Memory leak: c->code and c->classes (grown by emit/add_class
   via mrb_realloc) were never freed before raising, leaking on
   any compile error like /[/. c->stripped was already cleaned up
   here for the same reason; the other two buffers were missed.

2. Use-after-free: c->src aliases c->stripped when RE_FLAG_EXTENDED
   is set, but the original code freed c->stripped before passing
   c->src to mrb_raisef's "%s" formatter. Format the message into
   an mrb_value first (mruby's GC-managed string survives the
   longjmp), then free, then raise.

3. Heap-buffer-overflow: strip_extended returns a non-NUL-terminated
   buffer of size len. Even with format-before-free, "%s" called
   strlen and read past the buffer end. Use mruby's %l directive
   which takes an explicit (char*, size_t) and avoids strlen.

Reported by OSS-Fuzz (clusterfuzz testcase 5394267353972736).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-05 09:31:38 +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 479af5c1bd mruby-regexp: bounds-check non-ASCII RE_CHAR in first_set_walk
The first-byte bitmap (bm[16]) is intentionally ASCII-only
(include/re_internal.h:75 documents it as 128 bits / ASCII), and
the matcher at re_exec.c:39 short-circuits for bytes >= 128. But
first_set_walk's RE_CHAR case wrote bm[a >> 3] without checking
a, overflowing the 16-byte stack buffer for any pattern
containing a byte >= 128.

When a >= 128, return FALSE so compute_first_set marks the filter
unusable, matching the bail-out pattern already used for RE_NCLASS
and RE_ANY. The pattern still compiles and matches; only the
first-byte optimization is skipped.

Reported by OSS-Fuzz (clusterfuzz testcase 4909069193510912).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-03 22:43:22 +09:00
Yukihiro "Matz" Matsumoto c24d01c6fa Merge pull request #6776 from dearblue/array-combination.3
Return nil if a number less than 1 is passed to `Array#__combination_init`
2026-05-02 23:41:04 +09:00
Yukihiro "Matz" Matsumoto a18467da3f Merge pull request #6814 from dearblue/mruby-bin-mrb
Don't use `#puts` in bintest for "mruby-bin-mrb"
2026-05-02 23:37:26 +09:00
dearblue 525ab7a800 Return nil if a number less than 1 is passed to Array#__combination_init
This simplifies the subsequent processing.
2026-05-02 22:16:56 +09:00
dearblue a98c6b62ea Don't use #puts in bintest for "mruby-bin-mrb"
Since `Kernel#puts` is undefined in unit tests, using `Kernel#print` is required.
Previously, for example, running `rake test` with the following build configuration caused the tests to fail.

```ruby
MRuby::Build.new do
  toolchain
  enable_debug
  enable_test
  enable_bintest
  gem core: "mruby-bin-mrb"
end
```
2026-05-02 20:52:00 +09:00
Yukihiro "Matz" Matsumoto 7dfd560df8 mruby-io: cap puts recursion depth to prevent C stack overflow
io_puts_ary recursed unconditionally on nested arrays. For cyclic
arrays (a = []; a << a; puts a) or pathologically deep arrays,
this caused a C stack overflow.

Add a depth cap (IO_PUTS_MAX_DEPTH = 16); on overflow, write
"[...]\n" and return, matching CRuby's behavior on cycles. The
pattern mirrors mruby-set's MAX_NESTED_DEPTH for the same problem
shape (pure C recursion not dispatched as a Ruby method).

Reported by OSS-Fuzz (clusterfuzz testcase 6233530857488384).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-02 11:27:01 +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 adb27caaa2 Merge pull request #6813 from mruby/dependabot/pre_commit/pre-commit-hooks-4c01ccd002
build(deps): bump https://github.com/oxipng/oxipng from v10.1.0 to 10.1.1 in the pre-commit-hooks group
2026-05-01 11:01:42 +09:00
dependabot[bot] 7579a70974 build(deps): bump https://github.com/oxipng/oxipng
Bumps the pre-commit-hooks group with 1 update: [https://github.com/oxipng/oxipng](https://github.com/oxipng/oxipng).


Updates `https://github.com/oxipng/oxipng` from v10.1.0 to 10.1.1
- [Release notes](https://github.com/oxipng/oxipng/releases)
- [Changelog](https://github.com/oxipng/oxipng/blob/master/CHANGELOG.md)
- [Commits](https://github.com/oxipng/oxipng/compare/v10.1.0...v10.1.1)

---
updated-dependencies:
- dependency-name: https://github.com/oxipng/oxipng
  dependency-version: 10.1.1
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-30 14:59:05 +00:00
Yukihiro "Matz" Matsumoto 7e73392fc1 Merge pull request #6812 from DavidKorczynski/oss-fuzz-ext
Add new fuzzing harness to be consumed by OSS-Fuzz
2026-04-30 08:55:34 +09:00
David Korczynski 923c2e6a73 Add new fuzzing harness to be consumed by OSS-Fuzz
Adds 6 new fuzzing harnesses to be consumed by OSS-Fuzz. Have confirmed
locally this results in significant coverage gains relative to the
current code coverage in OSS-Fuzz:
https://storage.googleapis.com/oss-fuzz-coverage/mruby/reports/20260427/linux/src/report.html

Signed-off-by: David Korczynski <david@adalogics.com>
2026-04-29 06:49:03 -07:00
Yukihiro "Matz" Matsumoto 7c30c2f62b Merge pull request #6811 from mruby/dependabot/bundler/bundler-dependencies-59f8ea4567 2026-04-29 06:49:35 +09:00
dependabot[bot] fb41438cec build(deps): bump yard
Bumps the bundler-dependencies group with 1 update in the / directory: [yard](https://yardoc.org).


Updates `yard` from 0.9.42 to 0.9.43

---
updated-dependencies:
- dependency-name: yard
  dependency-version: 0.9.43
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-28 14:53:43 +00:00
Yukihiro "Matz" Matsumoto e4cd7e6daf mruby-compiler: place newline before else
The mruby C style places `else` on its own line. Reformat the
remaining `} else if (...)` occurrence in the C action block of
NODE_SYMBOLS dump.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-28 12:56:25 +09:00
Yukihiro "Matz" Matsumoto 16f1f4418a mruby-bigint: place newline before else
The mruby C style places `else` on its own line. Reformat the
remaining `} else {` occurrences.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-28 12:56:25 +09:00
Yukihiro "Matz" Matsumoto d6556195ef boxing_nan.h, boxing_word.h: place newline before else
The mruby C style places `else` on its own line. Reformat the
remaining `} else {` / `} else if (...)` occurrences.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-28 12:56:24 +09:00
Yukihiro "Matz" Matsumoto 1702b89c25 Merge pull request #6787 from dearblue/sprintf 2026-04-28 12:56:07 +09:00
Yukihiro "Matz" Matsumoto 06d236d535 mruby-bin-config: register installer in products instead of bins
`build.bins` is for compiled binaries with bare names; entries are
fed through `exefile()` in `tasks/bin.rake`, which appends
`exts.executable` (e.g. `.exe`) when no extension is present.

When `ENV['OS']` is not `Windows_NT` but the toolchain has
`exts.executable=".exe"` (e.g. cross builds, or environments where
`OS` is unset under visualcpp), `mruby-bin-config` ended up with a
file task at `.../mruby-config` while rake asked for
`.../mruby-config.exe`, aborting with "Don't know how to build task".

Skip the `bins` path and call `build.define_installer` directly with
`mruby_config_path`, mirroring the existing `iscross` branch. The
file task path now always matches the script content's extension
(`.bat` on Windows, no extension elsewhere).

close #6807, reported by UENO, M. (@eunos-1128).

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-25 19:57:44 +09:00
Yukihiro "Matz" Matsumoto baf4145fa1 Merge pull request #6806 from mruby/dependabot/bundler/bundler-dependencies-55d95e1de2 2026-04-24 14:07:17 +09:00
Yukihiro "Matz" Matsumoto b70d160273 Revert "gha: add hosts file workaround for Windows localhost resolution"
This reverts commit eed32b752b.

Windows's Winsock getaddrinfo does not consult the hosts file for
"localhost" because an internal hard-coded rule short-circuits first,
so the Add-Content step had no effect on the failing tests. Remove
the ineffective workaround now that a skip guard replaces it.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 08:19:24 +09:00
Yukihiro "Matz" Matsumoto 00e2c2fb29 Revert "gha: start DNS Client service and probe Windows localhost resolution"
This reverts commit 7ae25febbb.

CI runs with this change showed Dnscache was already Running on all
Windows jobs and Resolve-DnsName resolved "localhost" successfully,
yet Winsock getaddrinfo still failed. The service start and probe
had no effect on the failing tests, so they add noise without value.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 08:19:15 +09:00
Yukihiro "Matz" Matsumoto 550be1445f Revert "mruby-socket: add temporary getaddrinfo diagnostic for Windows CI"
This reverts commit a69d12aa54.

The diagnostic served its purpose: it isolated the failure to
Winsock's getaddrinfo for the "localhost" hostname. The permanent
fix (skip guard) is now in place, so remove the temporary probe.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 08:19:04 +09:00
Yukihiro "Matz" Matsumoto 57ca531f76 mruby-socket: skip localhost-dependent addrinfo tests on Windows
Addrinfo.getaddrinfo("localhost", ...) and Addrinfo.foreach("localhost",
...) crash on GitHub Actions Windows runners (Windows Server 2022 and
2025, both mingw-gcc and MSVC). Diagnostic showed 127.0.0.1 as a
numeric literal resolves fine via Winsock getaddrinfo, but "localhost"
as a hostname returns WSAHOST_NOT_FOUND under every address family
(AF_INET, AF_INET6, AF_UNSPEC, nil). PowerShell Resolve-DnsName on
the same runner succeeds, so the failure is Winsock-specific rather
than OS-level. Earlier workarounds (Dnscache service start, hosts
file append) had no effect.

Skip both tests on Windows with the existing SocketTest.win? guard,
matching the pattern already used for Addrinfo.unix and
Addrinfo#afamily. Linux and macOS coverage is unaffected.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 08:18:41 +09:00
Yukihiro "Matz" Matsumoto 7ae25febbb gha: start DNS Client service and probe Windows localhost resolution
Addrinfo.getaddrinfo on the Windows CI runners fails for "localhost"
regardless of address family (diagnostic commit a69d12aa54 showed
127.0.0.1 literal resolves fine while "localhost" never does under
AF_INET, AF_INET6, AF_UNSPEC, or nil). That rules out the earlier
KB4057932/AF_INET hypothesis and points at the Dnscache service
being stopped on the runner image.

Start Dnscache if not already running (and set it to Automatic),
then Resolve-DnsName localhost to print the OS-level resolver view
to the log. Existing hosts file and diagnostic assert stay in place
so the single CI round exposes both the service state and its
effect on getaddrinfo.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 01:39:08 +09:00
Yukihiro "Matz" Matsumoto a69d12aa54 mruby-socket: add temporary getaddrinfo diagnostic for Windows CI
Addrinfo.getaddrinfo("localhost", 53, AF_INET, SOCK_STREAM) crashes
on GitHub Actions Windows runners with WSAHOST_NOT_FOUND, while the
same call works on Linux and macOS. Earlier hypotheses (winsock link
missing; hosts file not populated) have been ruled out on CI.

Probe five variants in one run to isolate the failing condition:
numeric literal 127.0.0.1, localhost with AF_UNSPEC, localhost with
nil family, localhost with AF_INET6, and the original AF_INET path.
Results go to stdout via puts so they are visible in CI logs. This
commit is temporary and will be reverted once the real fix lands.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 01:20:15 +09:00
Yukihiro "Matz" Matsumoto eed32b752b gha: add hosts file workaround for Windows localhost resolution
GitHub Actions Windows Server 2022/2025 runner images ship with the
localhost entries commented out in C:\Windows\System32\drivers\etc\hosts
and rely on the DNS Client service's built-in rule. That path is subject
to Microsoft KB4057932 (getaddrinfo fails with WSAHOST_NOT_FOUND after
an AF_INET6 negative cache), which makes Addrinfo.getaddrinfo("localhost",
53, AF_INET, ...) in mrbgems/mruby-socket/test/addrinfo.rb flaky on CI.

Prepend explicit 127.0.0.1/::1 localhost entries to the hosts file on
all three Windows CI jobs (two mingw-gcc matrix entries and Windows-VC)
before running the build, so the resolver avoids the DNS negative cache
path and returns the expected IPv4 address.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-24 00:34:37 +09:00
dependabot[bot] e3dadc083c build(deps): bump rake in the bundler-dependencies group
Bumps the bundler-dependencies group with 1 update: [rake](https://github.com/ruby/rake).


Updates `rake` from 13.4.1 to 13.4.2
- [Release notes](https://github.com/ruby/rake/releases)
- [Changelog](https://github.com/ruby/rake/blob/master/History.rdoc)
- [Commits](https://github.com/ruby/rake/compare/v13.4.1...v13.4.2)

---
updated-dependencies:
- dependency-name: rake
  dependency-version: 13.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-23 14:58:23 +00:00
Yukihiro "Matz" Matsumoto 2ff6563484 mruby-task: restore Windows link library dropped during HAL migration
Commit be6413f0d86a ("mruby-task: migrate HAL to ports/ directories")
moved the Windows HAL source into mruby-task/ports/win/ but dropped
the linker.libraries declaration that previously lived in
hal-win-task/mrbgem.rake. Both mingw and MSVC builds now fail to link
task_hal.o/obj with undefined references to timeBeginPeriod,
timeEndPeriod, timeSetEvent, and timeKillEvent. Re-declare winmm
under a for_windows? guard.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 23:16:21 +09:00
Yukihiro "Matz" Matsumoto 3f27eac308 mruby-io: restore Windows link library dropped during HAL migration
Commit 9965f11cfe1c ("mruby-io: migrate HAL to ports/ directories")
moved the Windows HAL source into mruby-io/ports/win/ but dropped the
linker.libraries declaration that previously lived in
hal-win-io/mrbgem.rake. Both mingw and MSVC builds now fail to link
with undefined references to closesocket, WSAStartup, WSACleanup,
select, and WSAGetLastError referenced from io.c and io_hal.c.
Re-declare ws2_32 under a for_windows? guard.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 23:16:11 +09:00
Yukihiro "Matz" Matsumoto 22df8d61be mruby-socket: restore Windows link libraries dropped during HAL migration
Commit d9e107c801 ("mruby-socket: migrate HAL to ports/ directories")
moved the Windows HAL source into mruby-socket/ports/win/ but dropped
the linker.libraries declarations that previously lived in
hal-win-socket/mrbgem.rake. Both mingw and MSVC builds now fail to
link with undefined references to WSAStartup, socket, send, and other
Winsock APIs. Re-declare wsock32 and ws2_32 under a for_windows? guard.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 23:16:02 +09:00
Yukihiro "Matz" Matsumoto ad8fc7d918 mruby-bigint: add multi-precision gcd tests
Cover zero operands, power-of-2 fast path, negative operands,
balanced multi-limb pairs with a shared Fibonacci factor, highly
unbalanced pairs (to exercise the Euclidean fallback), and
Fibonacci neighbors (always coprime). Declare a test dependency
on mruby-numeric-ext since Integer#gcd is defined there.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 22:56:44 +09:00
Yukihiro "Matz" Matsumoto 8e91554c6d mruby-bigint: rewrite mpz_gcd main loop as binary Stein algorithm
Replace the classical Euclidean main loop (mpz_mod per iteration)
with Stein's binary GCD: subtract + factor out trailing 2s on
odd-maintained operands. Keep an mpz_mod fallback for heavily
unbalanced pairs (the smaller operand has at least two fewer limbs)
where one long division replaces many Stein subtracts.

The old loop allocated a temporary mpz_t every iteration to hold the
mod result; the Stein loop is allocation-free thanks to the in-place
paths in mpz_sub and mpz_div_2exp. This matches the "memory first"
priority and also happens to be faster on typical inputs because
mpz_mod's setup cost dominates when the quotient is small (the
classical Fibonacci-neighbor worst case).

Also add a small mpz_swap helper used by the new loop.

Benchmark (bin/mruby benchmark/bm_bigint_gcd.rb, median of 3):

  case                       before    after    ratio
  single-limb                 42 ms    50 ms    1.19  (fast-path
                                                      unchanged;
                                                      noise)
  fib(200) vs fib(201)        82 ms    23 ms    0.28
  balanced ~700-bit shared    46 ms    32 ms    0.70
  unbalanced big vs small     35 ms    28 ms    0.80
  power-of-2 path             38 ms    42 ms    1.11  (fast-path
                                                      unchanged;
                                                      noise)
  balanced ~2800-bit shared   19 ms    15 ms    0.79

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 22:52:05 +09:00
Yukihiro "Matz" Matsumoto ec55e0d0f1 mruby-bigint: correct mpz_gcd comment to describe hybrid behavior
The preceding comment claimed "Binary GCD (Stein's algorithm)",
but the multi-limb main loop is classical Euclidean using mpz_mod.
Only the prelude (factoring out common 2s, single-limb and
power-of-2 fast paths) is Stein-flavored. Describe what the code
actually does so readers are not misled.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 22:36:18 +09:00
Yukihiro "Matz" Matsumoto 7e8d74793c doc/guides/rom-method-table.md: sync with new MRB_MT_ENTRY shape
Per gemini-code-assist review on #6790: the documentation still
showed the pre-#6790 union mrb_mt_ptr member order (proc first)
and the C99 designated-initializer form of MRB_MT_ENTRY.  Update
both to match the merged code, and add a note explaining why func
must come first in the union.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:39 +09:00
Yukihiro "Matz" Matsumoto 81bcd4e931 mruby-bigint: simplify MPZ_CTX_INIT with positional initializer
Per gemini-code-assist review on #6791: replace member-by-member
assignment with C89/C++98-style positional aggregate initialization,
keeping the macro a single declaration and matching the existing
pool_storage initializer style in the same macro.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:39 +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 449040400a mruby-regexp: keep MatchData source/regexp GC-reachable
The mrb_match_data struct stores `source` and `regexp` as plain
mrb_value members of a C-allocated struct, which the GC does not
scan.  Under MRB_GC_STRESS the source string could be collected
while the MatchData was still alive, causing md[0] to read freed
memory (observed as "\xff\xff\xff").

Also stash source and regexp as instance variables on the MatchData
object so they remain reachable via the object's iv_tbl during GC.
The C struct members continue to provide fast direct access, and
no other call sites need to change.

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 86e274f759 irep.h: remove "not yet supported" comment from IREP_TT_BIGINT
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:38 +09:00
Yukihiro "Matz" Matsumoto d212c7be6c bm_ao_render.rb: use pack() for binary PPM output
Replace printf("%c", val) with pack("CCC") for outputting raw
bytes. printf("%c") with values >= 128 produces invalid UTF-8
on CRuby, corrupting the PPM output. Also remove unused
arguments from the "255\n" format string.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:38 +09:00
Yukihiro "Matz" Matsumoto 909f5a070f hw-adc: add ADC peripheral gem for embedded platforms
Add hw-adc gem with analog-to-digital conversion support:
- Common Ruby API with read, read_raw, and read_voltage
- ports/esp32/ using ESP-IDF ADC oneshot driver
- ports/rp2040/ using Pico SDK ADC hardware

Based on picoruby-adc by HASUMI Hitoshi.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:38 +09:00
Yukihiro "Matz" Matsumoto ce4bc77aaf hw-pwm: add PWM peripheral gem for embedded platforms
Add hw-pwm gem with pulse width modulation support:
- Common Ruby API with frequency, duty, period_us, pulse_width_us
- ports/esp32/ using LEDC peripheral
- ports/rp2040/ using Pico SDK PWM hardware

Based on picoruby-pwm by HASUMI Hitoshi.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:38 +09:00
Yukihiro "Matz" Matsumoto 5931a96a17 hw-spi: add SPI peripheral gem for embedded platforms
Add hw-spi gem with SPI communication support:
- Common Ruby API with write, read, and full-duplex transfer
- ports/esp32/ using ESP-IDF SPI master driver
- ports/rp2040/ using Pico SDK

Based on picoruby-spi by HASUMI Hitoshi.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:38 +09:00
Yukihiro "Matz" Matsumoto be6413f0d8 mruby-task: migrate HAL to ports/ directories
Move hal-posix-task and hal-win-task into
mruby-task/ports/posix/ and mruby-task/ports/win/.
Remove HAL auto-detection logic from mrbgem.rake. Update
cosmopolitan.rb to use conf.ports :posix instead of explicit
hal-* gem references.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:37 +09:00
Yukihiro "Matz" Matsumoto d9e107c801 mruby-socket: migrate HAL to ports/ directories
Move hal-posix-socket and hal-win-socket into
mruby-socket/ports/posix/ and mruby-socket/ports/win/.
Remove HAL auto-detection logic from mrbgem.rake.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:37 +09:00
Yukihiro "Matz" Matsumoto 886bf270f0 mruby-dir: migrate HAL to ports/ directories
Move hal-posix-dir and hal-win-dir into mruby-dir/ports/posix/
and mruby-dir/ports/win/. Remove HAL auto-detection logic from
mrbgem.rake.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:37 +09:00
Yukihiro "Matz" Matsumoto 9965f11cfe mruby-io: migrate HAL to ports/ directories
Move hal-posix-io and hal-win-io into mruby-io/ports/posix/ and
mruby-io/ports/win/. Platform sources are now compiled
automatically based on conf.ports setting. Remove HAL
auto-detection logic from mrbgem.rake.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:37 +09:00
Yukihiro "Matz" Matsumoto a8c82b506f hw-uart: consolidate platform gems into ports/ directories
Move hw-esp32-uart and hw-rp2040-uart into hw-uart/ports/esp32/
and hw-uart/ports/rp2040/ using the new ports build system.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:37 +09:00
Yukihiro "Matz" Matsumoto 53a43e9d36 hw-gpio: consolidate platform gems into ports/ directories
Move hw-esp32-gpio and hw-rp2040-gpio into hw-gpio/ports/esp32/
and hw-gpio/ports/rp2040/ using the new ports build system.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:37 +09:00
Yukihiro "Matz" Matsumoto b8eccc7a38 hw-i2c: consolidate platform gems into ports/ directories
Move hw-esp32-i2c and hw-rp2040-i2c into hw-i2c/ports/esp32/
and hw-i2c/ports/rp2040/ using the new ports build system.
Platform sources are now compiled automatically based on
conf.ports setting. Separate platform gems are no longer needed.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:36 +09:00
Yukihiro "Matz" Matsumoto 0e44db114c build: add ports support for platform-specific gem sources
Add conf.ports method to specify target platform tags (e.g.,
:esp32, :rp2040, :posix). Gems with matching ports/<name>/
directories will automatically compile those sources.

Host builds auto-detect :posix or :win when not explicitly set.
Cross builds require explicit specification. Existing gems
without ports/ directories are unaffected.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:36 +09:00
Yukihiro "Matz" Matsumoto 2b5a389de3 hw-uart: add UART peripheral gems for embedded platforms
Add three new gems for UART serial communication:
- hw-uart: common Ruby API, C bindings, ring buffer, and HAL header
- hw-esp32-uart: ESP32 HAL using ESP-IDF UART driver with FreeRTOS
  RX task
- hw-rp2040-uart: RP2040 HAL using Pico SDK with IRQ-driven RX

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:36 +09:00
Yukihiro "Matz" Matsumoto bdc3a65601 mruby-io: call mrb_hal_io_init/final from gem_init/gem_final
The HAL init/final functions were defined in hal-posix-io and
hal-win-io but never called. On Windows, hal-win-io performs
WSAStartup/WSACleanup in these functions, which was not being
invoked by mruby-io unlike other HAL gems (dir, socket, task).

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:36 +09:00
Yukihiro "Matz" Matsumoto 0ff41e4e28 hw-gpio: add GPIO peripheral gems for embedded platforms
Add three new gems for GPIO pin control:
- hw-gpio: common Ruby API, C bindings, and HAL header
- hw-esp32-gpio: ESP32 HAL using ESP-IDF GPIO driver
- hw-rp2040-gpio: RP2040 HAL using Pico SDK

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:36 +09:00
Yukihiro "Matz" Matsumoto 1ed52461e8 hw-i2c: add I2C peripheral gems for embedded platforms
Add three new gems for I2C communication:
- hw-i2c: common Ruby API, C bindings, and HAL header
- hw-esp32-i2c: ESP32 HAL using ESP-IDF I2C master driver
- hw-rp2040-i2c: RP2040 HAL using Pico SDK

The HAL API provides init, read, write, and atomic write_read
(repeated START) operations. Platform gems depend on hw-i2c
and are only compiled when explicitly included in build config.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:35 +09:00
Yukihiro "Matz" Matsumoto 416793db3d mruby-sprintf: use mrb_uint cast instead of uint64_t
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:35 +09:00
Yukihiro "Matz" Matsumoto 6f202d6c2b mruby-io: use mrb_int consistently in IO HAL interface
Replace all int64_t, uint64_t, uint32_t, and int32_t with mrb_int
in the HAL struct, timeval, and function signatures. All values
originate from or end up as mrb_int at the Ruby layer. Also removes
dead overflow checks in callers and the stdint.h dependency from
io_hal.h.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:35 +09:00
Yukihiro "Matz" Matsumoto ad1fd9285c mirb.c: add fileno() to _fileno() mapping for MSVC
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:35 +09:00
Yukihiro "Matz" Matsumoto 29f84030ca bigint.c: fix signed/unsigned comparison warning in base conversion
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:35 +09:00
Yukihiro "Matz" Matsumoto b70c393039 mruby-io: use mrb_int for mrb_hal_io_readlink() return type
int64_t was unnecessary; readlink(2) returns ssize_t (bounded by
PATH_MAX), and the Windows HAL just raises NotImplementedError.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:35 +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 726d8febf3 mruby-regexp: cache Pike VM state in pattern to avoid per-exec malloc
Pre-allocate visited array and thread lists at compile time and
reuse them across re_exec calls. A cache_in_use flag detects
re-entrancy and falls back to malloc when needed.

Eliminates 3 malloc + 3 free per NFA execution for the common
non-re-entrant case. Combined with the literal fast path, brings
literal match? from 4.7x to 2.5x vs CRuby.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:34 +09:00
Yukihiro "Matz" Matsumoto 034a64346c mruby-regexp: add literal pattern fast path bypassing NFA
Pure literal patterns (/hello/, /abc/) are detected at compile
time and matched using memchr+memcmp directly, completely
bypassing Pike VM setup (no malloc, no visited array, no thread
lists). Engine-only time drops from ~400ns to ~80ns.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:34 +09:00
Yukihiro "Matz" Matsumoto 8624764bca mruby-regexp: implement gsub/sub/scan core in C
Move the match loop for non-block gsub, sub, and scan from Ruby
to C. Key improvements:
- re_exec called directly in a loop without MatchData per match
- MatchData created only once (for $~ of the last match)
- Replacement \-escapes processed in C (apply_replacement)
- No intermediate string objects for pre_match/post_match
- Block variants remain in Ruby to avoid VM callbacks

Performance improvement (vs CRuby ratio):
  gsub simple: 5.0x -> 2.9x
  scan words:  5.8x -> 1.9x

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:34 +09:00
Yukihiro "Matz" Matsumoto 337b5906a2 mruby-regexp: add first-byte bitmap for fast position skipping
Compute a 128-bit bitmap of bytes that could start a match.
For patterns like /cat|dog|fox/ the bitmap contains only {b,c,d,f},
skipping positions where no alternative can match. For /\d+/ only
{'0'-'9'} are set.

Used when no literal prefix is available (alternation, character
class patterns). Falls back gracefully when too many bytes match.

Key improvements vs CRuby ratio:
  alternation miss: 9.5x -> 3.6x
  \d+ medium string: 3.0x -> 1.4x

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:33 +09:00
Yukihiro "Matz" Matsumoto 466e4ad84a mruby-regexp: defer pool_copy until character actually matches
Move pool_copy inside each match condition so captures are only
copied for threads that advance to the next step, skipping the
copy for non-matching threads.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:33 +09:00
Yukihiro "Matz" Matsumoto 1641c8c06f mruby-regexp: add literal prefix skip for fast string search
Extract the leading literal bytes from compiled bytecode and use
memchr + memcmp to skip positions where the prefix cannot match.
Both Pike VM and backtracking engine benefit.

For /needle/ in a 2006-char string: 29x faster (1.97s -> 0.07s),
now on par with CRuby/Oniguruma.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:33 +09:00
Yukihiro "Matz" Matsumoto 355c68f487 mruby-regexp: rename has_nongreedy to needs_backtrack
The flag is set not only for non-greedy quantifiers but also for
lookahead, lookbehind, and backreferences. The new name accurately
reflects its purpose: indicating that the backtracking engine is
required instead of the Pike VM.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:33 +09:00
Yukihiro "Matz" Matsumoto 13e63657a4 mruby-regexp: use array join in __sub_replace to avoid O(n^2)
Same pattern as the earlier gsub fix: collect parts in an array
and join at the end instead of repeated string concatenation.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:33 +09:00
Yukihiro "Matz" Matsumoto 6edef4e7e6 mruby-regexp: use dynamic captures allocation in exec_match()
Replace fixed RE_MAX_CAPTURES*2 (256 bytes) stack array with
malloc sized to actual pat->num_captures*2. Consistent with
the Pike VM's dynamic ncap-sized pool.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:33 +09:00
Yukihiro "Matz" Matsumoto 1f0809aad3 mruby-regexp: consolidate MatchData#captures and #to_a
Both methods had identical loop bodies, differing only in the
starting group index (1 vs 0). Extracted matchdata_to_ary()
with a from parameter.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:32 +09:00
Yukihiro "Matz" Matsumoto 2ef3a7c21e mruby-regexp: extract exec_match() to consolidate match methods
Regexp#match and Regexp#=~ shared most of their logic (get pattern,
execute, create MatchData, set globals). Extracted into exec_match()
internal function. Regexp#=~ now calls exec_match() and reads the
match position from the returned MatchData.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:32 +09:00
Yukihiro "Matz" Matsumoto 16b73ec67b mruby-regexp: extract get_iflags() helper to reduce duplication
The pattern of reading @flags IV and converting to uint32_t was
repeated in 6 methods. Consolidated into a single helper function.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:32 +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 101f8c69a1 mruby-regexp: implement fixed-length lookbehind assertions
Add (?<=...) positive and (?<!...) negative lookbehind support.
The sub-pattern must have a fixed byte length (no quantifiers or
alternation), computed at compile time and stored in the instruction.
At execution time, the engine backs up by that many bytes and runs
the sub-pattern forward. Maximum lookbehind length is 255 bytes.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:32 +09:00
Yukihiro "Matz" Matsumoto 4ded345ebb mruby-regexp: use array join in gsub to avoid O(n^2) concatenation
Collect replacement parts in an array and join at the end instead
of repeated string += which creates intermediate string objects.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:31 +09:00
Yukihiro "Matz" Matsumoto 9d955ba75f mruby-regexp: skip capture tracking in match-only path
When match? calls re_exec with captures=NULL, the Pike VM now
skips all capture pool operations: no pool_copy, no RE_SAVE
writes, no pool compaction. Only a single dummy pool slot is
allocated. This significantly reduces work for boolean matching.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:31 +09:00
Yukihiro "Matz" Matsumoto a2edda173b mruby-regexp: cache $1-$9 symbol IDs for match globals
Pre-intern the $1-$9 symbols on first use instead of calling
mrb_intern_cstr (which computes strlen + hash) on every match.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:31 +09:00
Yukihiro "Matz" Matsumoto 4579caa7d8 mruby-regexp: optimize Pike VM with pooled captures and generation counter
Major changes to the NFA execution engine:

- Thread captures stored in a flat pool sized to actual ncap
  (e.g. 4 ints for 1 capture group vs 64 fixed), dramatically
  reducing per-thread copy cost
- Generation counter for visited[] eliminates per-step memset
  of the entire bytecode-length array
- Pool compaction between steps reclaims dead thread slots
- Backtracking engine also uses dynamic ncap-sized captures

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:31 +09:00
Yukihiro "Matz" Matsumoto 5a158d32a8 mruby-regexp: support \& \` \' \+ \\ in sub/gsub replacements
Replacement strings now support:
  \& = full match, \` = pre_match, \' = post_match,
  \+ = last successful capture, \\ = literal backslash.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:31 +09:00
Yukihiro "Matz" Matsumoto 26dc5f76ea mruby-regexp: add MatchData#string, #regexp, and #to_s
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:31 +09:00
Yukihiro "Matz" Matsumoto 36a0f83db3 mruby-regexp: accept Regexp argument in Regexp.new
Regexp.new(regexp) copies the source and flags from the given
Regexp object, matching CRuby behavior.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:30 +09:00
Yukihiro "Matz" Matsumoto 893cb4edc4 mruby-regexp: fix Regexp#options to return Ruby constant values
Internal flags (MULTILINE=2, DOTALL=4, EXTENDED=8) differ from
Ruby constants (EXTENDED=2, MULTILINE=4). Convert in C instead
of returning raw internal flags. Also add Regexp#casefold?.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:30 +09:00
Yukihiro "Matz" Matsumoto 3f627c0d7d mruby-regexp: update README for x flag support
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:30 +09:00
Yukihiro "Matz" Matsumoto 78d761addf mruby-regexp: implement extended mode (x flag)
The x flag ignores unescaped whitespace and #comments in patterns,
making complex regexps more readable. Whitespace inside character
classes [...] remains literal. Implemented as a preprocessing step
that strips whitespace/comments before compilation.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:30 +09:00
Yukihiro "Matz" Matsumoto 0ca3192c9f mruby-regexp: implement Regexp#==, Regexp#eql?, and Regexp#hash
Two regexps are equal when they have the same source and flags.
Hash is computed from source string hash mixed with flags.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:30 +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 dab150007f mruby-regexp: implement Regexp#to_s in CRuby-compatible format
Regexp#to_s now returns (?flags:source) format (e.g. "(?i:abc)")
instead of the /source/flags format used by Regexp#inspect.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:29 +09:00
Yukihiro "Matz" Matsumoto 4d81275083 mruby-regexp: add $1-$9 globals and include in stdlib gembox
- $1-$9 global variables set by Regexp#match and Regexp#=~
- $1-$9 cleared to nil on match failure
- add mruby-regexp to stdlib.gembox (auto-included in standard builds)
- remove duplicate gem entry from host-debug.rb

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:29 +09:00
Yukihiro "Matz" Matsumoto 117af56bc2 mruby-regexp: add README.md
document supported syntax, Ruby API, engine architecture,
limitations, configuration, and license.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:29 +09:00
Yukihiro "Matz" Matsumoto 3c68e49178 mruby-regexp: add lookahead assertions (?=...) and (?!...)
implement positive and negative lookahead in the backtracking engine:
- (?=pattern): succeeds if pattern matches at current position
  without consuming characters
- (?!pattern): succeeds if pattern does NOT match at current position

new bytecodes RE_LOOKAHEAD and RE_NEG_LOOKAHEAD implemented in
the backtracking engine via nested bt_match calls.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:29 +09:00
Yukihiro "Matz" Matsumoto 7283560215 mruby-regexp: add named captures (?<name>...)
support named capture groups in patterns:
- compiler parses (?<name>...) syntax and builds name table
- MatchData#[:name] and MatchData#["name"] access by name
- MatchData#named_captures returns {name => value} hash
- Regexp#named_captures returns {name => group_number} hash
- named captures stored in mrb_regexp_pattern for GC safety

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:28 +09:00
Yukihiro "Matz" Matsumoto 8d92379d7c mruby-regexp: fix non-greedy quantifiers (*?, +?, ??)
non-greedy patterns now correctly match the shortest possible
string. patterns with non-greedy quantifiers are dispatched to
the backtracking engine which naturally handles non-greedy
semantics.

the Pike VM continues to be used for purely greedy patterns
(O(n*m) guarantee).

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:28 +09:00
Yukihiro "Matz" Matsumoto 23b2d24cf5 mruby-regexp: add backtracking engine for backreferences
add a recursive backtracking engine that handles \1-\9
backreferences. the Pike VM (NFA) is used for patterns without
backreferences; patterns with backreferences automatically
fall back to the backtracking engine.

the backtracking engine has a step limit (MRB_REGEXP_STEP_LIMIT,
default 1M) to prevent ReDoS on pathological patterns.

also adds SAVE backtracking (save/restore capture positions on
failed branches) for correct submatch tracking.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:28 +09:00
Yukihiro "Matz" Matsumoto 6deafd810f mruby-regexp: add edge case tests and improve coverage
add tests for: empty pattern, nested captures, word boundary \b,
non-capturing groups (?:), sub/gsub with block, scan with captures,
split with regexp, case/when with regexp, date reformatting.

known limitation: non-greedy quantifiers (*?, +?) currently behave
as greedy. needs match priority tracking (TODO for Phase 2).

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:28 +09:00
Yukihiro "Matz" Matsumoto 3bfb27b999 mruby-regexp: add /regex/ literal support, $~, Regexp.compile
- /regex/ literal syntax now works (compiler generates Regexp.compile)
- Regexp#match and Regexp#=~ set $~ global variable
- Regexp.last_match(n) for accessing capture groups
- Regexp.compile as alias for Regexp.new
- Regexp#options

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:27 +09:00
Yukihiro "Matz" Matsumoto 1cfa153ff3 mruby-regexp: add built-in regexp engine with Pike VM
implement a lightweight NFA-based regular expression engine for mruby:

engine (src/re_compile.c, src/re_exec.c, src/re_utf8.c):
- Pike VM (Thompson NFA simulation) with O(n*m) time guarantee
- ReDoS-resistant by design (no backtracking for basic patterns)
- supports: literals, ., *, +, ?, {n,m}, [], [^], |, ()
- character classes: \d, \w, \s and negations
- anchors: ^, $, \A, \z, \Z, \b, \B
- flags: i (ignorecase), m (multiline/dotall)
- captures with MatchData

Ruby API (src/regexp.c, mrblib/string_regexp.rb):
- Regexp.new, #match, #match?, #=~, #===, #source, #inspect
- Regexp.escape, Regexp::IGNORECASE/MULTILINE constants
- MatchData#[], #captures, #to_a, #begin, #end, #pre_match, #post_match
- String#match, #match?, #=~, #sub, #gsub, #scan, #split

~1700 lines of C + ~120 lines of Ruby. no external dependencies.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:27 +09:00
Yukihiro "Matz" Matsumoto 30e41242ec doc/internal/gc.md: add practical tuning examples
add workload-specific GC tuning advice based on benchmark data:
- allocation-heavy: interval_ratio 400 for ~12% improvement
- real-time: step_limit for bounded pause times
- large buffers: malloc_threshold
- diagnosing GC overhead with GC.stat

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:27 +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 3f8e13da6f codegen.c: consolidate while/until loop codegen
merge codegen_while/codegen_until into codegen_loop, and
codegen_while_mod/codegen_until_mod into codegen_loop_mod.
each pair differed only in swapped constant-condition checks
(true_always/false_always) and jump opcode (OP_JMPNOT/OP_JMPIF).

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:26 +09:00
Yukihiro "Matz" Matsumoto 65e24f4083 codegen.c: consolidate codegen_dot2/codegen_dot3 into codegen_range
the two functions differed only in OP_RANGE_INC vs OP_RANGE_EXC.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:25 +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 16fbd56e4b mruby-task: extract task_create_common() from Task.new and mrb_create_task()
both functions shared identical task allocation, context
initialization, queue insertion, and priority preemption logic.

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 f3cd991771 mruby-enum-lazy: add Enumerator::Lazy#tap_each
add tap_each method that yields each element for side effects
(e.g. logging, debugging) and passes it through unmodified.
see https://bugs.ruby-lang.org/issues/21520

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 4d03f40204 doc/internal/gc.md: update for debt model and new tuning parameters
Document the debt-based GC trigger model, malloc threshold,
step limit, GC.stat, and tuning guide.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:23 +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 9a736d6609 mruby-bin-mrb: remove mruby-compiler dependency from runtime executor
mrb_ccontext functions are implemented in mruby-compiler (y.tab.c),
causing linker errors when the compiler is excluded from the build.
Replace with mrb_load_irep_file() which is in core (src/load.c).
Also fix incorrect *argv in error message and remove dead fname field.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:23 +09:00
Yukihiro "Matz" Matsumoto eb8c177824 mruby-bin-mrb: add compiler-free runtime executor gem
The `mrb` command executes only precompiled RiteBinary (.mrb) files
without depending on mruby-compiler. This enables smaller binaries
for embedded deployments where scripts are precompiled on a
development machine.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:22 +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 5148062516 mruby-env: add README.md
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:21 +09:00
Yukihiro "Matz" Matsumoto 3272955bf1 mruby-env: add ENV object for environment variable access
ENV is a plain Object with singleton methods and Enumerable,
matching CRuby's behavior. C methods wrap getenv/setenv/unsetenv
with platform support for POSIX, macOS, and Windows.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:21 +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 b2d935b6d3 Merge pull request #6803 from saeki-mototsune/fix_psp_build_config 2026-04-23 19:24:10 +09:00
Yukihiro "Matz" Matsumoto 36dd668f73 Merge pull request #6799 from dearblue/mrb_state 2026-04-23 19:20:43 +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
Yukihiro "Matz" Matsumoto 7a14bad5ea Merge pull request #6777 from dearblue/array-combination.4 2026-04-23 18:59:05 +09:00
Yukihiro "Matz" Matsumoto bfc1f37b91 Merge pull request #6775 from dearblue/array-combination.2 2026-04-23 18:56:46 +09:00
Yukihiro "Matz" Matsumoto 7eab8302b0 Merge pull request #6774 from dearblue/array-combination.1 2026-04-23 18:49:28 +09:00
Yukihiro "Matz" Matsumoto 05f7236586 Merge pull request #6805 from mruby/dependabot/github_actions/github-actions-dependencies-f3e34333ea 2026-04-23 16:01:46 +09:00
Yukihiro "Matz" Matsumoto d359182b47 Merge pull request #6804 from mruby/dependabot/bundler/bundler-dependencies-39953ac9c0 2026-04-23 15:52:01 +09:00
dependabot[bot] ccd661b429 build(deps): bump github/codeql-action
Bumps the github-actions-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 4.35.1 to 4.35.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4.35.1...v4.35.2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-22 14:54:14 +00:00
dependabot[bot] aea5d584f0 build(deps): bump rake in the bundler-dependencies group
Bumps the bundler-dependencies group with 1 update: [rake](https://github.com/ruby/rake).


Updates `rake` from 13.3.1 to 13.4.1
- [Release notes](https://github.com/ruby/rake/releases)
- [Changelog](https://github.com/ruby/rake/blob/master/History.rdoc)
- [Commits](https://github.com/ruby/rake/compare/v13.3.1...v13.4.1)

---
updated-dependencies:
- dependency-name: rake
  dependency-version: 13.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: bundler-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 14:53:28 +00:00
SaekiMototsune 08b6d2ecd0 Disable some gems on build_config for playstationportable 2026-04-21 18:50:14 +09:00
Yukihiro "Matz" Matsumoto f469e7567a Merge pull request #6801 from mruby/dependabot/github_actions/github-actions-dependencies-9527efa922 2026-04-21 08:34:06 +09:00
dependabot[bot] 86940418c7 build(deps): bump the github-actions-dependencies group with 2 updates
Bumps the github-actions-dependencies group with 2 updates: [actions/cache](https://github.com/actions/cache) and [j178/prek-action](https://github.com/j178/prek-action).


Updates `actions/cache` from 5.0.4 to 5.0.5
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/668228422ae6a00e4ad889ee87cd7109ec5666a7...27d5ce7f107fe9357f9df03efb73ab90386fccae)

Updates `j178/prek-action` from 2.0.1 to 2.0.2
- [Release notes](https://github.com/j178/prek-action/releases)
- [Commits](https://github.com/j178/prek-action/compare/53276d8b0d10f8b6672aa85b4588c6921d0370cc...cbc2f23eb5539cf20d82d1aabd0d0ecbcc56f4e3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-dependencies
- dependency-name: j178/prek-action
  dependency-version: 2.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-20 17:20:54 +00:00
Yukihiro "Matz" Matsumoto 7546d53a3a Merge pull request #6800 from mruby/stable 2026-04-20 18:54:55 +09:00
mimaki 831da26b90 Update version and release date. (mruby 4.0.0 (2026-04-20)) 2026-04-20 17:43:06 +09:00
dearblue 0d00743a5f Define the typedef for mrb_state earlier
This improves consistency with other definitions.
2026-04-19 21:17:18 +09:00
Yukihiro "Matz" Matsumoto 0112fe6659 Merge pull request #6796 from mruby/dependabot/pre_commit/pre-commit-hooks-be5b5e25f5 2026-04-18 08:29:15 +09:00
Yukihiro "Matz" Matsumoto 605ef4919b Merge pull request #6797 from mruby/dependabot/github_actions/github-actions-dependencies-fd00acb19b 2026-04-18 08:28:56 +09:00
Yukihiro "Matz" Matsumoto e1ed206cae Merge pull request #6798 from mruby/dependabot/bundler/yard-0.9.42 2026-04-18 08:28:31 +09:00
Yukihiro "Matz" Matsumoto 82b91d15c5 Merge pull request #6794 from mattn/fix/readfloat-18-digits 2026-04-18 08:27:58 +09:00
dependabot[bot] f80f825bcf build(deps): bump yard from 0.9.38 to 0.9.42
Bumps [yard](https://yardoc.org) from 0.9.38 to 0.9.42.

---
updated-dependencies:
- dependency-name: yard
  dependency-version: 0.9.42
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-17 22:33:28 +00:00
dependabot[bot] eb51c63196 build(deps): bump github/codeql-action
Bumps the github-actions-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 4 to 4.35.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4...v4.35.1)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-17 14:53:59 +00:00
dependabot[bot] c2145c8642 build(deps): bump https://github.com/rubocop/rubocop
Bumps the pre-commit-hooks group with 1 update: [https://github.com/rubocop/rubocop](https://github.com/rubocop/rubocop).


Updates `https://github.com/rubocop/rubocop` from v1.86.0 to 1.86.1
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.86.0...v1.86.1)

---
updated-dependencies:
- dependency-name: https://github.com/rubocop/rubocop
  dependency-version: 1.86.1
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-16 14:57:37 +00: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
Yukihiro "Matz" Matsumoto a6a92bf95b Merge pull request #6793 from mattn/fix/readfloat-correctly-rounded 2026-04-16 17:09:10 +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
Yukihiro "Matz" Matsumoto 2f5a24ed9b Merge pull request #6792 from mruby/fix/host-cxx-build-dir 2026-04-15 15:34:09 +09:00
Yukihiro "Matz" Matsumoto 984f23a94e Merge pull request #6788 from mruby/dependabot/github_actions/github-actions-dependencies-d2d9db5a03 2026-04-15 09:49:36 +09:00
Yukihiro "Matz" Matsumoto 2575735152 Merge pull request #6791 from mruby/fix/bigint-cxx-compat 2026-04-15 09:33:59 +09:00
Yukihiro "Matz" Matsumoto eb87d59941 Merge pull request #6790 from mruby/fix/mrb-mt-entry-cxx-compat 2026-04-15 09:33:23 +09:00
Yukihiro "Matz" Matsumoto 8b44a52176 host-cxx: use distinct build directory from host-debug
build_config/host-cxx.rb defaulted to MRuby::Build.new (no name),
which is equivalent to MRuby::Build.new('host'), so it shared
build/host/ with build_config/host-debug.rb.

The two configs produce ABI-incompatible object files (C linkage
vs. C++-mangled).  Switching between configs without a clean step
silently mixes stale objects, leading to confusing undefined
reference errors at link time.

Name the C++ build 'host-cxx' so it gets its own build/host-cxx/
directory.  No external paths reference build/host/ in a way that
this change would break.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-15 09:30:33 +09:00
Yukihiro "Matz" Matsumoto 617a55b775 mruby-bigint: avoid C99 compound literal in MPZ_CTX_INIT
MPZ_CTX_INIT used a compound literal with designated initializers:

  mpz_ctx_t ctx##_struct = ((mpz_ctx_t){.mrb = ..., .pool = ...});

Both features are C99-only, and are not accepted by legacy C++
compilers (notably gcc 4.x) when mruby is pulled into a C++
translation unit via the -cxx.cxx wrapper.

Replace with plain member assignment so the macro expands to code
that is valid under C89/C++98 as well.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-15 09:01:19 +09:00
Yukihiro "Matz" Matsumoto a33ccd67b1 class.h: avoid C99 designated initializers in MRB_MT_ENTRY
Older C++ compilers (notably gcc 4.x) do not support C99 struct field
designators (.func = ...) even as an extension, which blocks building
mruby when it is included from a C++ translation unit under such
toolchains.

Reorder union mrb_mt_ptr so that mrb_func_t is the first member, and
switch MRB_MT_ENTRY to positional aggregate initialization.  Both are
compatible with pre-C99 / pre-C++20 compilers.

Fixes #6789

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-15 07:31:11 +09:00
dependabot[bot] c43eba8979 build(deps): bump softprops/action-gh-release
Bumps the github-actions-dependencies group with 1 update: [softprops/action-gh-release](https://github.com/softprops/action-gh-release).


Updates `softprops/action-gh-release` from 2 to 2.6.1
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-13 16:11:02 +00: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
dearblue 9ad94a50e7 Supplement to #6781
`mrb_str_dup()` always duplicates string objects in an unfrozen state, and the class is also set.
Therefore, it can be observed and modified from the Ruby side using the `ObjectSpace.each_object` method.

By using `mrb_str_dup_frozen()`, unnecessary duplication can be avoided, and modifications to the string can also be prevented.
2026-04-13 21:49:59 +09:00
dearblue d8e0d24fea Add mrb_str_dup_frozen() 2026-04-13 21:49:59 +09:00
Chris Hasiński a6283dbdf3 Clear existing aspec bits before setting new ones in mrb_proc_set_cfunc_aspec 2026-04-13 13:40:35 +02:00
Chris Hasiński 55f0228bf8 Store compressed aspec on cfunc RProc for correct arity/parameters
Compress the 24-bit aspec into 13 free flag bits on RProc (bits 0-6
and 14-19) when wrapping cfunc methods. Field widths: req/opt 3 bits
(max 7), post/key 2 bits (max 3), rest/kdict/block 1 bit each. Values
exceeding the compressed range are clamped and rest is forced to 1.

This enables Proc#arity and Proc#parameters to return correct results
for cfunc-backed Procs (e.g. from Method#to_proc) with zero memory
overhead -- no struct change needed.

Closes #6764
2026-04-13 13:11:27 +02:00
mimaki 65eb1df5a3 Update version to 4.0.0RC4. 2026-04-13 18:54:26 +09:00
mimaki 3b483db61d Merge branch 'master' into stable 2026-04-13 18:51:16 +09:00
Yukihiro "Matz" Matsumoto d65f7a9c5a Merge pull request #6784 from mruby/fix/news-security-updates 2026-04-13 09:01:38 +09:00
Yukihiro "Matz" Matsumoto e0aadaf6c4 NEWS.md: add security fixes and merged PRs (#6780, #6781, #6783)
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-13 08:39:21 +09:00
Yukihiro "Matz" Matsumoto dfd4eb52cc Merge pull request #6783 from jbampton/pin-actions 2026-04-13 08:16:14 +09:00
John Bampton ebed2d32a2 gha: pin workflows to hash 2026-04-11 17:51:28 +10:00
Yukihiro "Matz" Matsumoto 48fc4220d3 Merge pull request #6781 from mruby/fix/sprintf-uaf 2026-04-11 16:33:05 +09:00
Yukihiro "Matz" Matsumoto 18ba02662b Merge pull request #6780 from mruby/fix/string-prepend-overflow 2026-04-11 16:32:24 +09:00
Yukihiro "Matz" Matsumoto 59552ecb8e mruby-sprintf: protect format string from mutation during callbacks
mrb_str_format captured raw C pointers (p, end) into the format
string's buffer before the main loop. The %s and %p specifiers call
to_s and inspect, which can invoke Ruby code that mutates the format
string via String#replace, freeing or reallocating its buffer. The
loop then continued iterating with dangling pointers, reading freed
memory and potentially leaking adjacent heap contents into the result.

Duplicate the format string with mrb_str_dup() before the loop. This
is O(1) because mrb_str_dup shares the underlying buffer; if the
original is later mutated via String#replace, str_replace decrements
the shared refcount, leaving our duplicate's buffer intact.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-10 14:51:59 +09:00
Yukihiro "Matz" Matsumoto af6f23ddb3 mruby-string-ext: fix String#prepend with self-referencing arguments
String#prepend(s, s) read RSTRING_LEN(argv[i]) in the copy loop after
mrb_str_resize had already updated the receiver's length, causing the
memcpy to write past the allocated buffer.

Detect self-references with mrb_obj_eq() and read from the memmoved
original data at p + total_prepend_len using the captured self_len.
This also handles mixed cases like s.prepend("X", s) where earlier
writes would otherwise corrupt the source of later reads.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-10 14:44:46 +09:00
Yukihiro "Matz" Matsumoto 4eb4884219 Merge pull request #6778 from mruby/dependabot/github_actions/github-actions-dependencies-6289042708 2026-04-09 07:55:37 +09:00
dependabot[bot] c394525da6 build(deps): bump super-linter/super-linter
Bumps the github-actions-dependencies group with 1 update: [super-linter/super-linter](https://github.com/super-linter/super-linter).


Updates `super-linter/super-linter` from 8.5.0 to 8.6.0
- [Release notes](https://github.com/super-linter/super-linter/releases)
- [Changelog](https://github.com/super-linter/super-linter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/super-linter/super-linter/compare/v8.5.0...v8.6.0)

---
updated-dependencies:
- dependency-name: super-linter/super-linter
  dependency-version: 8.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-08 14:54:46 +00:00
dearblue d246ac6c8a Make Array#__combination_next return an array of elements
Since the main processing will be completed on the C side, the Ruby side will simply call the block.
2026-04-08 22:29:56 +09:00
dearblue 5e3e982442 Improve the calculation of the next index for Array#__combination_next
When the index wraps around, the lower index becomes a fixed value.
2026-04-08 22:16:59 +09:00
dearblue 3a9ef9d27e Avoid using the deprecated function mrb_data_check_and_get()
The "d" directive in `mrb_get_args()` can be used as an alternative.
Furthermore, NULL checking is unnecessary for the following reasons:
  - Incomplete objects from `ary_combination_init()` are not passed to the caller and are garbage collected when `ObjectSpace.each_object` is called, so they are never retrieved
  - Even if `state.clone` is called, the `RData::type` of the cloned object is set to NULL, so it is rejected by `mrb_get_args()`
2026-04-08 21:10:45 +09:00
Yukihiro "Matz" Matsumoto 5d5cf0c3ea Merge pull request #6772 from mruby/fix/update-news 2026-04-02 20:33:46 +09:00
Yukihiro "Matz" Matsumoto 3a5c45f282 NEWS.md: update for recent changes
Add entries for language changes (case/in NoMatchingPatternError,
compound statement in tLPAREN_ARG), C API additions (mrb_bigint_p(),
RVALUE union), compiler optimizations (literal chunking), build
fixes (MSYS2), security fixes, and 26 merged pull requests.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-02 20:17:19 +09:00
mimaki 6c121b9708 Update version to 4.0.0RC3. 2026-04-02 11:23:31 +09:00
mimaki d06cb40725 Merge branch 'master' into stable 2026-04-02 10:31:49 +09:00
Yukihiro "Matz" Matsumoto 0cc4caad2b Merge pull request #6769 from khasinski/fix-socket-recvfrom-nonblock 2026-04-01 09:23:00 +09:00
Yukihiro "Matz" Matsumoto 0cef3e5414 Merge pull request #6770 from jbampton/patch-2 2026-04-01 09:17:38 +09:00
Yukihiro "Matz" Matsumoto 825d4c388e Merge pull request #6771 from mruby/dependabot/pre_commit/pre-commit-hooks-820b35f878 2026-04-01 09:16:17 +09:00
dependabot[bot] 141a8bc406 build(deps): bump https://github.com/rhysd/actionlint
Bumps the pre-commit-hooks group with 1 update: [https://github.com/rhysd/actionlint](https://github.com/rhysd/actionlint).


Updates `https://github.com/rhysd/actionlint` from v1.7.11 to 1.7.12
- [Release notes](https://github.com/rhysd/actionlint/releases)
- [Changelog](https://github.com/rhysd/actionlint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rhysd/actionlint/compare/v1.7.11...v1.7.12)

---
updated-dependencies:
- dependency-name: https://github.com/rhysd/actionlint
  dependency-version: 1.7.12
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 15:00:01 +00:00
John Bampton a6aa0fdb24 Dependabot: add cooldown to pre-commit ecosystem 2026-04-01 00:46:24 +10:00
Chris Hasiński b85c520843 Remove stale self-corruption workaround in recvfrom_nonblock
The s = self workaround and XXX comment in recvfrom_nonblock date back
to the initial import of mruby-socket. The underlying bug where self
became a SystemcallException inside ensure blocks has since been fixed.

Verified that self correctly refers to the socket object in ensure
blocks after exceptions from recvfrom.
2026-03-30 22:14:26 +02:00
Yukihiro "Matz" Matsumoto d522bd5aa2 Merge pull request #6768 from hasumikin/fix/envadjust 2026-03-30 21:18:06 +09:00
HASUMI Hitoshi d95ebe4a23 Fix stack extension bug causing HardFault
This patch fixes a bug in the stack extension logic that could cause a HardFault on certain configurations when the stack is reallocated to a new address.

## Background

When the mruby VM's stack runs out, stack_extend_alloc() calls mrb_realloc to grow it.
If reallocation moves the block to a new address, envadjust() adjusts all ci->stack pointers to point into the new allocation.

## The bug

The bug happened under the configuration below:

- MRB_INT64 on MRB_32BIT (`sizeof(mrb_value) == 16` because MRB_NO_BOXING is now mandatory)
- Allocator with 8-byte alignment (eg. PICORB_ALLOC_ALIGN=8 in PicoRuby for Raspi Pico)

The delta was computed via mrb_value* pointer subtraction:

```c
ptrdiff_t delta = newbase - oldbase;  // units of sizeof(mrb_value)
```

If :
- Old address: 0x2004c508
- New address: 0x2004c510 (8-byte difference)

The pointer subtraction truncated: 8 / 16 = 0.
envadjust() was misleaded as `delta == 0` and returned early without adjusting any ci->stack pointers.
The stbase was updated to the new address, but all stack pointers still pointed 8 bytes before it.
Every register access was shifted, reading garbage, ultimately causing a HardFault.

## The fix

Byte-level char* calculation instead of mrb_value* calculation:

```c
ptrdiff_t off = (char*)newbase - (char*)oldbase;
// ...
ci->stack = (mrb_value*)((char*)ci->stack + off);
```

This ensures the adjustment is exact regardless of sizeof(mrb_value) and allocator alignment.
2026-03-30 16:25:46 +09:00
Yukihiro "Matz" Matsumoto ac9f7aedd2 Merge pull request #6767 from mruby/fix/lparen-arg-compstmt 2026-03-30 07:54:31 +09:00
Yukihiro "Matz" Matsumoto 919cbd8fea mruby-compiler: allow compound statement in tLPAREN_ARG
Change the grammar rule for tLPAREN_ARG from accepting only a
single stmt to accepting compstmt. This allows compound
statements with semicolons inside parenthesized arguments when
the parenthesis is preceded by a space, e.g., `p (f1; f2)`.

This matches the behavior of CRuby 3.3+.

Fixes #6766.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-30 07:06:30 +09:00
Yukihiro "Matz" Matsumoto 801eefeab6 Merge pull request #6765 from khasinski/fix-lazy-flat-map 2026-03-29 22:55:13 +09:00
Chris Hasiński 8f71887e46 Improve flat_map test descriptions for clarity 2026-03-28 23:14:46 +01:00
Chris Hasiński 3f52ef6cfc Fix Lazy#flat_map to handle non-enumerable block return values
When the block passed to Lazy#flat_map returns a non-enumerable value
(e.g. an Integer), mruby raised NoMethodError because it unconditionally
called #each on the result. CRuby yields non-enumerable values directly.

Use respond_to?(:each) to match CRuby behavior: iterate enumerable
results, yield non-enumerable results as-is.
2026-03-28 22:59:11 +01:00
Yukihiro "Matz" Matsumoto 28c5b1b17b Merge pull request #6763 from mruby/dependabot/github_actions/github-actions-dependencies-19ba90ca6e 2026-03-26 14:27:05 +09:00
dependabot[bot] d50932c50f build(deps): bump j178/prek-action
Bumps the github-actions-dependencies group with 1 update: [j178/prek-action](https://github.com/j178/prek-action).


Updates `j178/prek-action` from 1 to 2
- [Release notes](https://github.com/j178/prek-action/releases)
- [Commits](https://github.com/j178/prek-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: j178/prek-action
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-25 14:54:41 +00:00
Yukihiro "Matz" Matsumoto b7e3743130 Merge pull request #6762 from mruby/fix-test-build-race 2026-03-25 15:49:46 +09:00
Yukihiro "Matz" Matsumoto 805e6dbc33 Merge pull request #6761 from mruby/fix-gc-unregister-leak 2026-03-25 15:44:44 +09:00
Yukihiro "Matz" Matsumoto e8c5e7c0cd mruby-test: write generated C files atomically to avoid race condition
With `rake -m`, the C compiler can start reading a partially-written
gem_test.c before generation completes. Write to a .tmp file first,
then rename to the final path.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-25 15:32:36 +09:00
Yukihiro "Matz" Matsumoto ab249864cc gc.c: remove all matching entries in mrb_gc_unregister()
Previously only the first match was removed, leaking duplicate
entries when the same object was registered multiple times.
Use two-pointer compaction for O(N) removal.

Fixes #6760.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-25 15:29:27 +09:00
Yukihiro "Matz" Matsumoto c2b588139f Merge pull request #6758 from dearblue/vm 2026-03-25 15:17:02 +09:00
Yukihiro "Matz" Matsumoto 21dc829903 Merge pull request #6759 from dearblue/bigint 2026-03-25 15:15:49 +09:00
dearblue 6c4a8c09db Define mrb_bigint_p() always.
Define the `mrb_bigint_p()` macro function, which returns false if `MRB_USE_BIGINT` is undefined.
2026-03-24 22:16:15 +09:00
dearblue c52faebb7f Don't assign the result of mrb_funcall() directly to regs
There are two reasons:

  - If the mruby call stack is extended, the `ci` variable may become invalid.
  - The C language does not specify the order in which the left-hand and right-hand sides of an assignment expression are evaluated.
    Therefore, if the mruby data stack is extended, `ci->stack` may become invalid.
2026-03-24 21:25:46 +09:00
Yukihiro "Matz" Matsumoto f61ab96689 Merge pull request #6756 from dearblue/array-ext 2026-03-24 15:04:58 +09:00
Yukihiro "Matz" Matsumoto 07a6c6e56b Merge pull request #6757 from mruby/dependabot/pre_commit/pre-commit-hooks-ae77450b09 2026-03-24 09:30:46 +09:00
dependabot[bot] 45ebb3d0c8 build(deps): bump https://github.com/rubocop/rubocop
Bumps the pre-commit-hooks group with 1 update: [https://github.com/rubocop/rubocop](https://github.com/rubocop/rubocop).


Updates `https://github.com/rubocop/rubocop` from v1.85.1 to 1.86.0
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.85.1...v1.86.0)

---
updated-dependencies:
- dependency-name: https://github.com/rubocop/rubocop
  dependency-version: 1.86.0
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 15:23:09 +00:00
dearblue 2135088ada Avoid the impact of object modifications caused by calls to mrb_vm_exec()
Several methods defined in mruby-array-ext are written in C and may call `mrb_vm_exec()`.
If array objects are modified on the Ruby side, problems may arise in subsequent processing.

  - Using objects that have been removed from the array and garbage collected
  - Using pointers or array lengths that have become invalid due to changes to the array object
  - Modifying the contents of a shared array object directly

ref: https://github.com/mruby/mruby/issues/6662
2026-03-22 23:05:37 +09:00
Yukihiro "Matz" Matsumoto 04f998238e Merge pull request #6755 from leviongit/vm/karg/delete-return 2026-03-21 17:03:27 +09:00
Yukihiro "Matz" Matsumoto affcbb000b Merge pull request #6754 from leviongit/core/attr_accessor/nullary 2026-03-21 17:02:24 +09:00
Yukihiro "Matz" Matsumoto ff5e6a2491 Merge pull request #6753 from dearblue/array.product 2026-03-21 16:57:49 +09:00
leviongit e8d0750458 reload ci after mrb_hash_delete_key 2026-03-20 21:35:26 +01:00
leviongit 88e356e7da remove redundant mrb_hash_get call
`mrb_hash_delete` returns the removed element (which is guaranteed to
exist due to the `mrb_hash_key_p` check), this prevents the hash from
being searched twice.
2026-03-20 21:20:18 +01:00
leviongit 95ece95e37 prefer marking the procs implementing attr_reader methods as noarg
this commit works on #6752 so it doesn't require a call to
`mrb_get_args`
2026-03-20 19:14:54 +01:00
dearblue 98d763603c Further optimize Array#product
Replace `__product_group` method with `__product_generate` and `__product_next`.
This change eliminates the need for Ruby to perform internal state calculations, allowing it to simply receive the results.
2026-03-20 21:13:52 +09:00
Yukihiro "Matz" Matsumoto 01ce2f8c71 Merge pull request #6747 from katafrakt/handle-hash-default-arg 2026-03-20 16:44:27 +09:00
Yukihiro "Matz" Matsumoto f08dede1d9 Merge pull request #6752 from khasinski/fix-attr-reader-arity 2026-03-20 16:40:45 +09:00
Yukihiro "Matz" Matsumoto 3bfe703f80 Merge pull request #6750 from dearblue/array.product 2026-03-20 10:24:10 +09:00
Yukihiro "Matz" Matsumoto e82fa70004 Merge pull request #6749 from hasumikin/fix/microcontroller-profile 2026-03-20 10:22:32 +09:00
Yukihiro "Matz" Matsumoto 8d53f65b96 Merge pull request #6748 from jbampton/clean-up-workflows 2026-03-20 10:21:45 +09:00
Chris Hasiński c0b1e87c09 Fix attr_reader-generated methods accepting extra arguments
attr_reader-generated getter methods silently ignored any arguments
passed to them. CRuby raises ArgumentError in this case.

Add mrb_get_args(mrb, "") to enforce zero arguments, matching CRuby.
2026-03-19 23:12:14 +01:00
dearblue 8441eaf633 Fixed "Out-of-bounds Read" and "Divide-by-Zero" in ary_product_group()
Reproduction:

  - Out-of-bounds Read

    ```console
    % build/host/bin/mruby -e '([nil] * 256).__product_group([[nil] * 256], 1 << 32, 256)'
    zsh: segmentation fault (core dumped)  build/host/bin/mruby -e
    ```

  - Divide-by-Zero

    ```console
    % build/host/bin/mruby -e '([nil] * 256).__product_group([[]], 1 << 32, 256)'
    zsh: floating point exception (core dumped)  build/host/bin/mruby -e '([nil] * 256).__product_group([[]], 1 << 32, 256)'
    ```
2026-03-19 23:11:06 +09:00
HASUMI Hitoshi 736a72cdc9 [skip ci] Update include/mrbconf.h
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-19 09:50:15 +09:00
HASUMI Hitoshi 5e9eed2d0c Fix KHASH_DEFAULT_SIZE to KHASH_INITIAL_SIZE rename inconsistencies
Commit 250bf6edd renamed KHASH_DEFAULT_SIZE to KHASH_INITIAL_SIZE but
missed updating build_config files and documentation. Also restore the
default value in khash.h to 32, consistent with the documented default
and the profile hierarchy (MRB_CONSTRAINED_BASELINE_PROFILE reduces it
to 16).
2026-03-19 09:38:36 +09:00
John Bampton 80d84188fd pre-commit bump Node.js 2026-03-18 17:57:33 +10:00
John Bampton 55ef8e1728 Update workflows 2026-03-18 17:57:05 +10:00
Paweł Świątkowski 13d9d770fc Correctly handle empty hash as default named argument
```
def func(arg: {})
  p arg
end
```

This used to work in earlier mruby versions, but broke somewhere recently.
2026-03-18 08:53:20 +01:00
Yukihiro "Matz" Matsumoto a41eeaed33 Merge pull request #6735 from dearblue/presym 2026-03-16 21:55:22 +09:00
Yukihiro "Matz" Matsumoto 4ed4326dd5 Merge pull request #6746 from mruby/dependabot/pre_commit/pre-commit-hooks-6e50fbf5c1 2026-03-16 21:47:31 +09:00
dearblue 000acedc35 Prevent full recompilation without changes to presym file
Commit b9a1a1fb23 is a revert of commit 8df9a22a85, differing only in the comment.
This means the issue from https://github.com/mruby/mruby/issues/6721 has reappeared.

The cause of https://github.com/mruby/mruby/issues/6721, as stated in the commit message for commit 8df9a22a85, is that each ".o" file has an indirect dependency on all ".pi" files through the presym file.

This patch therefore adds a proxy-like task `gensym:update:#{build.name}` between tasks.
Its purpose is to hide the direct dependency from ".o" files to the presym file from the rake system.
2026-03-14 17:14:37 +09:00
dependabot[bot] 14e98c60c3 build(deps): bump the pre-commit-hooks group across 1 directory with 8 updates
Bumps the pre-commit-hooks group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [https://github.com/gitleaks/gitleaks](https://github.com/gitleaks/gitleaks) | `v8.30.0` | `8.30.1` |
| [https://github.com/oxipng/oxipng](https://github.com/oxipng/oxipng) | `v10.0.0` | `10.1.0` |
| [https://github.com/Lucas-C/pre-commit-hooks](https://github.com/Lucas-C/pre-commit-hooks) | `v1.5.5` | `1.5.6` |
| [https://github.com/rhysd/actionlint](https://github.com/rhysd/actionlint) | `v1.7.9` | `1.7.11` |
| [https://github.com/codespell-project/codespell](https://github.com/codespell-project/codespell) | `v2.4.1` | `2.4.2` |
| [https://github.com/igorshubovych/markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli) | `v0.46.0` | `0.48.0` |
| [https://github.com/rubocop/rubocop](https://github.com/rubocop/rubocop) | `v1.81.7` | `1.85.1` |
| [https://github.com/adrienverge/yamllint](https://github.com/adrienverge/yamllint) | `v1.37.1` | `1.38.0` |



Updates `https://github.com/gitleaks/gitleaks` from v8.30.0 to 8.30.1
- [Release notes](https://github.com/gitleaks/gitleaks/releases)
- [Commits](https://github.com/gitleaks/gitleaks/compare/v8.30.0...v8.30.1)

Updates `https://github.com/oxipng/oxipng` from v10.0.0 to 10.1.0
- [Release notes](https://github.com/oxipng/oxipng/releases)
- [Changelog](https://github.com/oxipng/oxipng/blob/master/CHANGELOG.md)
- [Commits](https://github.com/oxipng/oxipng/compare/v10.0.0...v10.1.0)

Updates `https://github.com/Lucas-C/pre-commit-hooks` from v1.5.5 to 1.5.6
- [Release notes](https://github.com/Lucas-C/pre-commit-hooks/releases)
- [Commits](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.5.5...v1.5.6)

Updates `https://github.com/rhysd/actionlint` from v1.7.9 to 1.7.11
- [Release notes](https://github.com/rhysd/actionlint/releases)
- [Changelog](https://github.com/rhysd/actionlint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rhysd/actionlint/compare/v1.7.9...v1.7.11)

Updates `https://github.com/codespell-project/codespell` from v2.4.1 to 2.4.2
- [Release notes](https://github.com/codespell-project/codespell/releases)
- [Commits](https://github.com/codespell-project/codespell/compare/v2.4.1...v2.4.2)

Updates `https://github.com/igorshubovych/markdownlint-cli` from v0.46.0 to 0.48.0
- [Release notes](https://github.com/igorshubovych/markdownlint-cli/releases)
- [Commits](https://github.com/igorshubovych/markdownlint-cli/compare/v0.46.0...v0.48.0)

Updates `https://github.com/rubocop/rubocop` from v1.81.7 to 1.85.1
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.81.7...v1.85.1)

Updates `https://github.com/adrienverge/yamllint` from v1.37.1 to 1.38.0
- [Release notes](https://github.com/adrienverge/yamllint/releases)
- [Changelog](https://github.com/adrienverge/yamllint/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/adrienverge/yamllint/compare/v1.37.1...v1.38.0)

---
updated-dependencies:
- dependency-name: https://github.com/gitleaks/gitleaks
  dependency-version: 8.30.1
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
- dependency-name: https://github.com/oxipng/oxipng
  dependency-version: 10.1.0
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
- dependency-name: https://github.com/Lucas-C/pre-commit-hooks
  dependency-version: 1.5.6
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
- dependency-name: https://github.com/rhysd/actionlint
  dependency-version: 1.7.11
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
- dependency-name: https://github.com/codespell-project/codespell
  dependency-version: 2.4.2
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
- dependency-name: https://github.com/igorshubovych/markdownlint-cli
  dependency-version: 0.48.0
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
- dependency-name: https://github.com/rubocop/rubocop
  dependency-version: 1.85.1
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
- dependency-name: https://github.com/adrienverge/yamllint
  dependency-version: 1.38.0
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-13 14:57:23 +00:00
mimaki 9d523e2f74 Update version to 4.0.0RC2. 2026-03-12 19:07:19 +09:00
mimaki 380c459d37 Merge branch 'master' into stable 2026-03-12 19:01:29 +09:00
Yukihiro "Matz" Matsumoto eb7d0b86d7 Merge pull request #6736 from jbampton/add-dependabot-pre-commit 2026-03-12 17:16:48 +09:00
John Bampton 9bdf998faa Add pre-commit ecosystem to Dependabot
Group dependabot updates to reduce repo noise

Add descriptive group labels

https://github.blog/changelog/2026-03-10-dependabot-now-supports-pre-commit-hooks/

https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#package-ecosystem-
2026-03-12 16:59:11 +10:00
Yukihiro "Matz" Matsumoto 457a50b485 Merge pull request #6744 from mruby/fix/case-in-no-match-error 2026-03-12 14:36:46 +09:00
Yukihiro "Matz" Matsumoto 143959b94b doc: fix prettier formatting in markdown files
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-12 14:33:37 +09:00
Yukihiro "Matz" Matsumoto d8de35b635 codegen.c: raise NoMatchingPatternError in case/in without else
case/in without else clause now raises NoMatchingPatternError
when no pattern matches, matching CRuby behavior. Fixes #6741.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-12 14:28:17 +09:00
Yukihiro "Matz" Matsumoto b34b7206f9 Merge pull request #6738 from jbampton/add-manual-hooks-workflow 2026-03-12 14:20:02 +09:00
Yukihiro "Matz" Matsumoto 03d1f7d6ec Merge pull request #6743 from mruby/fix/words-chunking 2026-03-12 14:17:38 +09:00
Yukihiro "Matz" Matsumoto 9239a9e0ef pre-commit: fix prettier formatting in doc/guides/capi.md
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-12 14:05:48 +09:00
Yukihiro "Matz" Matsumoto 62cf0dc17a codegen.c: chunk %w() and %i() literals to reduce register pressure
Apply the same chunking strategy used for regular array literals
to %w() and %i() literal arrays in gen_literal_array(). Fixes #6740.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-12 12:12:50 +09:00
Yukihiro "Matz" Matsumoto 12af8f513e Merge pull request #6742 from mruby/fix/remove-markdown-link-check 2026-03-12 11:52:45 +09:00
Yukihiro "Matz" Matsumoto ab378ec01e Merge pull request #6690 from jbampton/cleanup-prettier-pre-commit 2026-03-12 11:37:25 +09:00
Yukihiro "Matz" Matsumoto 2d00f4a284 Merge pull request #6739 from mimaki/fix-msys2-build-error 2026-03-12 11:36:53 +09:00
mimaki 77f6ffecc7 Updates build script to support MSYS2 drive letters, fixing build error with MSYS2. 2026-03-11 11:31:55 +09:00
John Bampton 626b2fa469 Put manual hooks in separate workflow file 2026-03-11 10:29:57 +10:00
mimaki 1d34c3bed5 Update version to 4.0.0RC. 2026-03-05 11:58:41 +09:00
mimaki 18a1c33fd4 Merge remote-tracking branch 'origin' into stable 2026-03-05 11:14:03 +09:00
John Bampton 95b74eefdf pre-commit: add hook to ensure Makefiles are indented with tabs
Manual hook added which runs a shell script.

Can be run locally manually by Linux or Mac users and we have this also running on the GitHub Actions CI.

Will fail if Makefiles uses spaces for indentation.

Can run the manual hooks with:

`pre-commit run check-makefile-indentation --all-files --hook-stage manual`
2025-12-30 03:30:04 +10:00
John Bampton 3c7d19d41b prettier: set pass_filenames: false to avoid multiple passes
Updated `.prettierignore` to ingnore the typical Python environment files from `.venv`

If you are running pre-commit locally you probably have a Python environment setup.

This PR speeds up the prettier hook and avoids multiple passes through the targetted files.
2025-12-30 01:53:03 +10:00
Yukihiro "Matz" Matsumoto 40436e4e3c Merge pull request #6627 from suetanvil/stable-3.4.0-plus-fixes 2025-09-23 07:54:35 +01:00
Hendrik 651a183228 fix bigint on raspberry pi
this fixes an issue where base can be out of range on a raspberry pi.
2025-09-18 12:39:05 -04:00
John Bampton be3212906a pre-commit add official meta hook check-useless-excludes 2025-07-29 13:19:06 +10:00
dearblue 40371ebf01 Generate the string object last at the end of processing
This is simpler than processing `suffix` after the string object has been created.
2025-07-21 10:42:31 +09:00
dearblue d230bb16d4 Use end pointers instead of lengths
For processing simplicity.
2025-07-21 10:42:31 +09:00
dearblue 3f27133639 Take a const char * for the "z" specifier of mrb_get_args() 2025-07-21 10:39:11 +09:00
dearblue f72a32ad87 Improvements to the Windows implementation of File.basename
This change is mainly to remove `_splitpath()`.

The reason is:

  - UNC paths are not supported.
  - The behavior is not guaranteed to be consistent when passing a UTF-8 string, since the handling of multibyte characters changes depending on the set codepage.
  - Only up to 255 bytes can be processed.

Therefore, it is preferable to integrate with non-Windows implementations to ensure consistent implementation behavior.
2025-07-21 10:39:11 +09:00
dearblue 1f7bc64845 Added test for File.basename 2025-07-21 10:16:38 +09:00
dearblue 627452e340 Sharing arrays with ary_dup()
Until now, the array entity has always been allocated from the heap and copied.
However, this allows the array entity to be shared with the source if possible.
2025-07-18 23:01:28 +09:00
dearblue 3c653fc0d4 Share array entities if possible with ary.replace(frozen_ary)
Currently mruby is limited to always placing array entities on the rewritable heap.
Therefore, if the original array was frozen and only rewritable objects survived the subsequent process, the array entity can be changed.
If an array object that actually shared the array entity is frozen with `ary.freeze`, there should be no problem, since the shared state is still kept.
2025-07-18 22:51:00 +09:00
dearblue b667b052dd Remove iterating variable from mrb_objspace_each_objects() 2024-08-25 20:47:26 +09:00
dearblue ef763285e9 Reduced description of mrb_init_core()
Since `mrb_init_math()` does not exist, it is no longer declared as a prototype.
2024-03-27 22:24:42 +09:00
take_cheeze 0bd391bc6f Fix return type of symbol slicing.
Thanks to @suzukake .
2014-07-28 16:30:14 +09:00
take_cheeze 2249cf7c7f Implement Symbol#slice and Symbol#[] in mruby-symbol-ext. 2014-07-28 16:30:14 +09:00
250 changed files with 20191 additions and 6324 deletions
+18
View File
@@ -5,11 +5,29 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
groups:
bundler-dependencies:
patterns:
- "*"
cooldown: cooldown:
default-days: 7 default-days: 7
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
groups:
github-actions-dependencies:
patterns:
- "*"
cooldown:
default-days: 7
- package-ecosystem: "pre-commit"
directory: "/"
schedule:
interval: "daily"
groups:
pre-commit-hooks:
patterns:
- "*"
cooldown: cooldown:
default-days: 7 default-days: 7
+10 -4
View File
@@ -31,7 +31,9 @@ jobs:
LD: ${{ matrix.cc }} LD: ${{ matrix.cc }}
steps: steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Ruby version - name: Ruby version
run: ruby -v run: ruby -v
- name: Compiler version - name: Compiler version
@@ -44,11 +46,13 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Ruby version - name: Ruby version
run: ruby -v run: ruby -v
- name: Cache cosmocc - name: Cache cosmocc
uses: actions/cache@v5 uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: cache-cosmocc id: cache-cosmocc
with: with:
path: ~/cosmo path: ~/cosmo
@@ -70,7 +74,9 @@ jobs:
MRUBY_CONFIG: ci/msvc MRUBY_CONFIG: ci/msvc
steps: steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Ruby version - name: Ruby version
run: ruby -v run: ruby -v
- name: Build and test - name: Build and test
+6 -4
View File
@@ -18,14 +18,16 @@ jobs:
language: ["actions"] language: ["actions"]
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v4 uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v4 uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4 uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
with: with:
category: "Security" category: "Security"
+7 -2
View File
@@ -2,6 +2,9 @@ name: Coverage
on: [push] on: [push]
permissions:
contents: read
jobs: jobs:
coverage: coverage:
name: Coverage name: Coverage
@@ -16,7 +19,9 @@ jobs:
LDFLAGS: --coverage LDFLAGS: --coverage
steps: steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Ruby version - name: Ruby version
run: ruby -v run: ruby -v
- name: Compiler version - name: Compiler version
@@ -34,7 +39,7 @@ jobs:
echo \`\`\` echo \`\`\`
} > "$GITHUB_STEP_SUMMARY" } > "$GITHUB_STEP_SUMMARY"
- name: Upload coverage report - name: Upload coverage report
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: coverage-${{ github.sha }} name: coverage-${{ github.sha }}
path: coverage/ path: coverage/
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
pull-requests: write pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/labeler@v6 - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
with: with:
repo-token: "${{ secrets.GITHUB_TOKEN }}" repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true sync-labels: true
+4 -2
View File
@@ -11,7 +11,9 @@ jobs:
name: Run ls-lint name: Run ls-lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ls-lint/action@v2.3.1 with:
persist-credentials: false
- uses: ls-lint/action@02e380fe8733d499cbfc9e22276de5085508a5bd # v2.3.1
with: with:
config: .github/linters/.ls-lint.yml config: .github/linters/.ls-lint.yml
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
fuzz-seconds: 600 fuzz-seconds: 600
dry-run: false dry-run: false
- name: Upload Crash - name: Upload Crash
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: failure() if: failure()
with: with:
name: artifacts name: artifacts
+22
View File
@@ -0,0 +1,22 @@
# https://github.com/j178/prek
name: Manual hooks
on: [pull_request]
permissions:
contents: read
jobs:
pre-commit:
name: Run pre-commit
runs-on: ubuntu-latest
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
with:
install-only: true
- name: Run manual pre-commit hooks
run: prek run --color=always --all-files --hook-stage manual
+4 -4
View File
@@ -12,9 +12,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: j178/prek-action@v1 with:
persist-credentials: false
- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
with: with:
extra-args: --all-files extra-args: --all-files
- name: Run manual pre-commit hooks
run: prek run --color=always --all-files --hook-stage manual
+4 -2
View File
@@ -19,7 +19,9 @@ jobs:
fail-fast: false fail-fast: false
steps: steps:
- name: "Checkout ${{ github.ref_name }} ( ${{ github.sha }} )" - name: "Checkout ${{ github.ref_name }} ( ${{ github.sha }} )"
uses: actions/checkout@v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Builds - name: Builds
id: builds id: builds
run: | run: |
@@ -38,7 +40,7 @@ jobs:
mv .sha256 "$packagename.sha256" mv .sha256 "$packagename.sha256"
) )
- name: Release - name: Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with: with:
draft: true draft: true
prerelease: ${{ contains(github.ref_name, '-rc') }} prerelease: ${{ contains(github.ref_name, '-rc') }}
+3 -2
View File
@@ -15,12 +15,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with: with:
# Full git history is needed to get a proper list of changed files within `super-linter` # Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0 fetch-depth: 0
persist-credentials: false
- name: Lint Code Base - name: Lint Code Base
uses: super-linter/super-linter/slim@v8.5.0 uses: super-linter/super-linter/slim@9e863354e3ff62e0727d37183162c4a88873df41 # v8.6.0
env: env:
# VALIDATE_BASH_EXEC: true # VALIDATE_BASH_EXEC: true
VALIDATE_DOCKERFILE_HADOLINT: true VALIDATE_DOCKERFILE_HADOLINT: true
+22 -9
View File
@@ -3,7 +3,7 @@
default_stages: [pre-commit, pre-push] default_stages: [pre-commit, pre-push]
default_language_version: default_language_version:
python: python3 python: python3
node: 24.11.1 node: 24.14.0
minimum_pre_commit_version: "3.2.0" minimum_pre_commit_version: "3.2.0"
exclude: "^tools/lrama/" exclude: "^tools/lrama/"
repos: repos:
@@ -15,6 +15,9 @@ repos:
- id: check-hooks-apply - id: check-hooks-apply
name: run check-hooks-apply name: run check-hooks-apply
description: check hooks apply to the repository description: check hooks apply to the repository
- id: check-useless-excludes
name: run check-useless-excludes
description: clean up unnecessary exclusion patterns
- repo: local - repo: local
hooks: hooks:
- id: prettier - id: prettier
@@ -24,6 +27,16 @@ repos:
files: \.(md|ya?ml)$ files: \.(md|ya?ml)$
language: node language: node
additional_dependencies: ["prettier@3.7.4"] additional_dependencies: ["prettier@3.7.4"]
pass_filenames: false
stages: [manual]
- id: check-makefile-indentation
name: check Makefiles are indented with tabs
description: ensures that Makefiles are indented with tabs
entry: ./scripts/check_makefiles_for_tabs.sh
language: system
files: "(?i)^makefile$"
pass_filenames: true # <-- Crucial change: pass filenames to the script
types: [file] # Ensure only regular files are passed, not directories
stages: [manual] stages: [manual]
- id: check-zip-file-is-not-committed - id: check-zip-file-is-not-committed
name: disallow zip files name: disallow zip files
@@ -34,13 +47,13 @@ repos:
track and have security implications. Please remove the zip file from the repository. track and have security implications. Please remove the zip file from the repository.
files: \.zip$ files: \.zip$
- repo: https://github.com/gitleaks/gitleaks - repo: https://github.com/gitleaks/gitleaks
rev: v8.30.0 rev: v8.30.1
hooks: hooks:
- id: gitleaks - id: gitleaks
name: run gitleaks name: run gitleaks
description: detect hardcoded secrets with gitleaks description: detect hardcoded secrets with gitleaks
- repo: https://github.com/oxipng/oxipng - repo: https://github.com/oxipng/oxipng
rev: v10.0.0 rev: v10.1.1
hooks: hooks:
- id: oxipng - id: oxipng
name: run oxipng name: run oxipng
@@ -72,7 +85,7 @@ repos:
- id: mixed-line-ending - id: mixed-line-ending
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/Lucas-C/pre-commit-hooks - repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.5 rev: v1.5.6
hooks: hooks:
- id: forbid-tabs - id: forbid-tabs
name: run no-tabs checker name: run no-tabs checker
@@ -84,19 +97,19 @@ repos:
args: [--whitespaces-count, "2"] args: [--whitespaces-count, "2"]
exclude: Makefile$ exclude: Makefile$
- repo: https://github.com/rhysd/actionlint - repo: https://github.com/rhysd/actionlint
rev: v1.7.9 rev: v1.7.12
hooks: hooks:
- id: actionlint - id: actionlint
name: run actionlint name: run actionlint
description: lint GitHub Actions workflow files description: lint GitHub Actions workflow files
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: v2.4.1 rev: v2.4.2
hooks: hooks:
- id: codespell - id: codespell
name: run codespell name: run codespell
description: check spelling with codespell description: check spelling with codespell
- repo: https://github.com/igorshubovych/markdownlint-cli - repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.46.0 rev: v0.48.0
hooks: hooks:
- id: markdownlint - id: markdownlint
name: run markdownlint name: run markdownlint
@@ -105,7 +118,7 @@ repos:
types: [markdown] types: [markdown]
files: \.md$ files: \.md$
- repo: https://github.com/rubocop/rubocop - repo: https://github.com/rubocop/rubocop
rev: v1.81.7 rev: v1.86.2
hooks: hooks:
- id: rubocop - id: rubocop
name: run rubocop name: run rubocop
@@ -119,7 +132,7 @@ repos:
name: run shellcheck name: run shellcheck
description: check shell scripts with a static analysis tool description: check shell scripts with a static analysis tool
- repo: https://github.com/adrienverge/yamllint - repo: https://github.com/adrienverge/yamllint
rev: v1.37.1 rev: v1.38.0
hooks: hooks:
- id: yamllint - id: yamllint
name: run yamllint name: run yamllint
+1
View File
@@ -3,3 +3,4 @@ build
coverage coverage
doc/internal/opcode.md doc/internal/opcode.md
tools/lrama tools/lrama
.venv
+16 -12
View File
@@ -1,25 +1,25 @@
# Authors of mruby (mruby developers) # Authors of mruby (mruby developers)
## The List of Contributors sorted by number of commits (as of 2026-03-02 02877f0) ## The List of Contributors sorted by number of commits (as of 2026-05-31 9d084b0)
7532 Yukihiro "Matz" Matsumoto (@matz)* 7747 Yukihiro "Matz" Matsumoto (@matz)*
712 dearblue (@dearblue)* 749 dearblue (@dearblue)*
587 KOBAYASHI Shuji (@shuujii) 587 KOBAYASHI Shuji (@shuujii)
353 Daniel Bovensiepen (@bovi)* 353 Daniel Bovensiepen (@bovi)*
345 Takeshi Watanabe (@take-cheeze)* 345 Takeshi Watanabe (@take-cheeze)*
333 Masaki Muranaka (@monaka) 333 Masaki Muranaka (@monaka)
255 John Bampton (@jbampton) 266 John Bampton (@jbampton)
234 Jun Hiroe (@suzukaze) 234 Jun Hiroe (@suzukaze)
228 Tomoyuki Sahara (@tsahara)* 228 Tomoyuki Sahara (@tsahara)*
220 Cremno (@cremno)* 220 Cremno (@cremno)*
209 Yuki Kurihara (@ksss)+ 209 Yuki Kurihara (@ksss)+
144 Yasuhiro Matsumoto (@mattn)* 146 Yasuhiro Matsumoto (@mattn)*
113 Carson McDonald (@carsonmcdonald) 113 Carson McDonald (@carsonmcdonald)
104 Tomasz Pędraszewski (@dabroz)* 104 Tomasz Pędraszewski (@dabroz)*
83 Akira Yumiyama (@akiray03)* 83 Akira Yumiyama (@akiray03)*
83 skandhas (@skandhas) 83 skandhas (@skandhas)
80 Masamitsu MURASE (@masamitsu-murase) 80 Masamitsu MURASE (@masamitsu-murase)
73 Hiroshi Mimaki (@mimaki)* 79 Hiroshi Mimaki (@mimaki)*
71 Tatsuhiko Kubo (@cubicdaiya)* 71 Tatsuhiko Kubo (@cubicdaiya)*
71 Yuichiro MASUI (@masuidrive) 71 Yuichiro MASUI (@masuidrive)
62 Yuichiro Kaneko (@yui-knk)+ 62 Yuichiro Kaneko (@yui-knk)+
@@ -36,8 +36,10 @@
32 Masayoshi Takahashi (@takahashim)+ 32 Masayoshi Takahashi (@takahashim)+
31 MATSUMOTO Ryosuke (@matsumotory)* 31 MATSUMOTO Ryosuke (@matsumotory)*
30 Nobuyoshi Nakada (@nobu) 30 Nobuyoshi Nakada (@nobu)
29 HASUMI Hitoshi (@hasumikin)
26 Hoshiumi Arata (@hoshiumiarata)* 26 Hoshiumi Arata (@hoshiumiarata)*
25 Julian Aron Prenner (@furunkel)* 25 Julian Aron Prenner (@furunkel)*
23 leviongit (@leviongit)
22 Clayton Smith (@clayton-shopify) 22 Clayton Smith (@clayton-shopify)
22 Uchio Kondo (@udzura)* 22 Uchio Kondo (@udzura)*
22 Zachary Scott (@zzak)* 22 Zachary Scott (@zzak)*
@@ -50,10 +52,9 @@
18 Corey Powell (@IceDragon200) 18 Corey Powell (@IceDragon200)
18 Hidetaka Takano (@TJ-Hidetaka-Takano) 18 Hidetaka Takano (@TJ-Hidetaka-Takano)
18 Jon Maken (@jonforums)+ 18 Jon Maken (@jonforums)+
18 leviongit (@leviongit)
18 mirichi (@mirichi) 18 mirichi (@mirichi)
17 Mitchell Blank Jr (@mitchblank)* 17 Mitchell Blank Jr (@mitchblank)*
16 HASUMI Hitoshi (@hasumikin) 16 Hendrik (@Asmod4n)
16 bggd (@bggd) 16 bggd (@bggd)
16 kano4 (@kano4) 16 kano4 (@kano4)
15 Felix Jones (@felixjones)* 15 Felix Jones (@felixjones)*
@@ -76,7 +77,7 @@
11 RIZAL Reckordp (@Reckordp)+ 11 RIZAL Reckordp (@Reckordp)+
11 Seeker (@SeekingMeaning) 11 Seeker (@SeekingMeaning)
11 takkaw (@takkaw) 11 takkaw (@takkaw)
10 Hendrik (@Asmod4n) 10 Chris Hasiński (@khasinski)
10 Miura Hideki (@miura1729) 10 Miura Hideki (@miura1729)
10 Narihiro Nakamura (@authorNari) 10 Narihiro Nakamura (@authorNari)
10 YAMAMOTO Masaya (pandax381) 10 YAMAMOTO Masaya (pandax381)
@@ -88,6 +89,7 @@
8 Wataru Ashihara (@wataash)* 8 Wataru Ashihara (@wataash)*
7 Bhargava Shastry (@bshastry)* 7 Bhargava Shastry (@bshastry)*
7 Kouichi Nakanishi (@keizo042) 7 Kouichi Nakanishi (@keizo042)
7 Paweł Świątkowski (@katafrakt)
7 Rubyist (@expeditiousRubyist) 7 Rubyist (@expeditiousRubyist)
7 Simon Génier (@simon-shopify) 7 Simon Génier (@simon-shopify)
7 Terence Lee (@hone) 7 Terence Lee (@hone)
@@ -101,7 +103,6 @@
6 INOUE Yasuyuki (@yasuyuki) 6 INOUE Yasuyuki (@yasuyuki)
6 Junji Sawada (@junjis0203) 6 Junji Sawada (@junjis0203)
6 Kenji Okimoto (@okkez)+ 6 Kenji Okimoto (@okkez)+
6 Paweł Świątkowski (@katafrakt)
6 Selman ULUG (@selman) 6 Selman ULUG (@selman)
6 Yusuke Endoh (@mame)* 6 Yusuke Endoh (@mame)*
6 buty4649 (@buty4649) 6 buty4649 (@buty4649)
@@ -120,7 +121,6 @@
5 dreamedge (@dreamedge) 5 dreamedge (@dreamedge)
5 nkshigeru (@nkshigeru) 5 nkshigeru (@nkshigeru)
5 xuejianqing (@joans321) 5 xuejianqing (@joans321)
4 Chris Hasiński (@khasinski)
4 Dante Catalfamo (@dantecatalfamo) 4 Dante Catalfamo (@dantecatalfamo)
4 Goro Kikuchi (@gorogit) 4 Goro Kikuchi (@gorogit)
4 Herwin Weststrate (@herwinw) 4 Herwin Weststrate (@herwinw)
@@ -140,6 +140,7 @@
4 Yuji Yamano (@yyamano) 4 Yuji Yamano (@yyamano)
4 kurodash (@kurodash)* 4 kurodash (@kurodash)*
4 wanabe (@wanabe)* 4 wanabe (@wanabe)*
2 0x1eef (@0x1eef)
3 Anton Davydov (@davydovanton) 3 Anton Davydov (@davydovanton)
3 Aurora Nockert (@auroranockert) 3 Aurora Nockert (@auroranockert)
3 Carlo Prelz (@asfluido)* 3 Carlo Prelz (@asfluido)*
@@ -192,6 +193,7 @@
2 Masahiro Wakame (@vvkame)+ 2 Masahiro Wakame (@vvkame)+
2 Minao Yamamoto (@tarosay)+ 2 Minao Yamamoto (@tarosay)+
2 Nihad Abbasov (@NARKOZ) 2 Nihad Abbasov (@NARKOZ)
2 Pete Kinnecom (@petekinnecom)
2 Robert Mosolgo (@rmosolgo) 2 Robert Mosolgo (@rmosolgo)
2 Russel Hunter Yukawa (@rhykw)+ 2 Russel Hunter Yukawa (@rhykw)+
2 Ryunosuke SATO (@tricknotes) 2 Ryunosuke SATO (@tricknotes)
@@ -220,6 +222,7 @@
1 Colin MacKenzie IV (@sinisterchipmunk) 1 Colin MacKenzie IV (@sinisterchipmunk)
1 Daehyub Kim (@lateau) 1 Daehyub Kim (@lateau)
1 Daniel Varga (@vargad) 1 Daniel Varga (@vargad)
1 David Korczynski (@DavidKorczynski)
1 Diamond Rivero (@diamant3) 1 Diamond Rivero (@diamant3)
1 Edgar Boda-Majer (@eboda) 1 Edgar Boda-Majer (@eboda)
1 Fangrui Song (@MaskRay) 1 Fangrui Song (@MaskRay)
@@ -274,7 +277,6 @@
1 Patrick Ellis (@pje) 1 Patrick Ellis (@pje)
1 Patrick Pokatilo (@SHyx0rmZ) 1 Patrick Pokatilo (@SHyx0rmZ)
1 Pavel Evstigneev (@Paxa)+ 1 Pavel Evstigneev (@Paxa)+
1 Pete Kinnecom (@petekinnecom)
1 Piotr Usewicz (@pusewicz) 1 Piotr Usewicz (@pusewicz)
1 Prayag Verma (@pra85) 1 Prayag Verma (@pra85)
1 Ranmocy (@ranmocy) 1 Ranmocy (@ranmocy)
@@ -282,6 +284,7 @@
1 Ryan Scott Lewis (@RyanScottLewis) 1 Ryan Scott Lewis (@RyanScottLewis)
1 Ryo Okubo (@syucream) 1 Ryo Okubo (@syucream)
1 SAkira a.k.a. Akira Suzuki (@sakisakira) 1 SAkira a.k.a. Akira Suzuki (@sakisakira)
1 SaekiMototsune (@saeki-mototsune)
1 Santiago Rodriguez (@sanrodari) 1 Santiago Rodriguez (@sanrodari)
1 Satoh, Hiroh (@cho45)+ 1 Satoh, Hiroh (@cho45)+
1 Satoru Naba (@snaba)+ 1 Satoru Naba (@snaba)+
@@ -325,6 +328,7 @@
1 sbsoftware (@sbsoftware) 1 sbsoftware (@sbsoftware)
1 ssmallkirby (@smallkirby) 1 ssmallkirby (@smallkirby)
1 taku toyama (@tsuichu) 1 taku toyama (@tsuichu)
1 vobloeb (@vobloeb)
`*` - Entries unified according to names and addresses `*` - Entries unified according to names and addresses
`+` - Entries with names different from commits `+` - Entries with names different from commits
+1 -1
View File
@@ -48,7 +48,7 @@ PROJECT_NAME = mruby
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = 3.4.0 PROJECT_NUMBER = 4.0.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a
+2 -2
View File
@@ -2,8 +2,8 @@ GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
coderay (1.1.3) coderay (1.1.3)
rake (13.3.1) rake (13.4.2)
yard (0.9.38) yard (0.9.44)
yard-coderay (0.1.0) yard-coderay (0.1.0)
coderay coderay
yard yard
+44
View File
@@ -23,6 +23,8 @@ mruby now supports pattern matching (case/in) syntax:
- Trailing comma in method definition parameters: `def foo(a, b,)` ([f78334b](https://github.com/mruby/mruby/commit/f78334b)) - Trailing comma in method definition parameters: `def foo(a, b,)` ([f78334b](https://github.com/mruby/mruby/commit/f78334b))
- Array/Hash/String subclasses can now override `[]` and `[]=` methods ([#6675](https://github.com/mruby/mruby/pull/6675)) - Array/Hash/String subclasses can now override `[]` and `[]=` methods ([#6675](https://github.com/mruby/mruby/pull/6675))
- `OP_SETIDX` optimization for Array and Hash ([ddd8fe1](https://github.com/mruby/mruby/commit/ddd8fe1)) - `OP_SETIDX` optimization for Array and Hash ([ddd8fe1](https://github.com/mruby/mruby/commit/ddd8fe1))
- `case`/`in` without `else` now raises `NoMatchingPatternError` ([d8de35b](https://github.com/mruby/mruby/commit/d8de35b))
- Allow compound statement in parenthesized argument context ([919cbd8](https://github.com/mruby/mruby/commit/919cbd8))
# Changes in C API # Changes in C API
@@ -37,6 +39,8 @@ mruby now supports pattern matching (case/in) syntax:
- `mrb_open()` returns mrb_state with exc set on init failure ([05ffe0c](https://github.com/mruby/mruby/commit/05ffe0c)) - `mrb_open()` returns mrb_state with exc set on init failure ([05ffe0c](https://github.com/mruby/mruby/commit/05ffe0c))
- `mrb_utf8_to_buf()` for UTF-8 encoding consolidation ([7e28e68](https://github.com/mruby/mruby/commit/7e28e68)) - `mrb_utf8_to_buf()` for UTF-8 encoding consolidation ([7e28e68](https://github.com/mruby/mruby/commit/7e28e68))
- `kh_is_end()` macro for safe khash iteration ([893cc75](https://github.com/mruby/mruby/commit/893cc75)) - `kh_is_end()` macro for safe khash iteration ([893cc75](https://github.com/mruby/mruby/commit/893cc75))
- `mrb_bigint_p()` always defined regardless of bigint gem presence ([6c4a8c0](https://github.com/mruby/mruby/commit/6c4a8c0))
- `RInteger` and `RFloat` added to `RVALUE` union ([13dbca0](https://github.com/mruby/mruby/commit/13dbca0))
# ROM Method Tables # ROM Method Tables
@@ -76,6 +80,7 @@ mruby-symbol-ext, mruby-range-ext, mruby-object-ext.
- Emscripten: use native WASM exception handling ([ca364e3](https://github.com/mruby/mruby/commit/ca364e3)) - Emscripten: use native WASM exception handling ([ca364e3](https://github.com/mruby/mruby/commit/ca364e3))
- HAL (Hardware Abstraction Layer) for platform abstraction in mruby-io, mruby-socket, mruby-dir, mruby-task ([74ca22f](https://github.com/mruby/mruby/commit/74ca22f)) - HAL (Hardware Abstraction Layer) for platform abstraction in mruby-io, mruby-socket, mruby-dir, mruby-task ([74ca22f](https://github.com/mruby/mruby/commit/74ca22f))
- `MRUBY_MIRB_READLINE` environment variable to control readline library selection ([0aafb83](https://github.com/mruby/mruby/commit/0aafb83)) - `MRUBY_MIRB_READLINE` environment variable to control readline library selection ([0aafb83](https://github.com/mruby/mruby/commit/0aafb83))
- MSYS2 drive letter support in build script ([77f6ffe](https://github.com/mruby/mruby/commit/77f6ffe))
- Inter-gem headers separated from external API headers ([#6671](https://github.com/mruby/mruby/pull/6671)) - Inter-gem headers separated from external API headers ([#6671](https://github.com/mruby/mruby/pull/6671))
# Changes in mrbgems # Changes in mrbgems
@@ -110,6 +115,7 @@ mruby-symbol-ext, mruby-range-ext, mruby-object-ext.
## Other Gem Changes ## Other Gem Changes
- **_NOTE_**: `Hash#deconstruct_keys` removed for CRuby compatibility ([34b9412](https://github.com/mruby/mruby/commit/34b9412)) - **_NOTE_**: `Hash#deconstruct_keys` removed for CRuby compatibility ([34b9412](https://github.com/mruby/mruby/commit/34b9412))
- **mruby-enum-lazy**: Fix `Lazy#flat_map` to handle non-enumerable block return values ([#6765](https://github.com/mruby/mruby/pull/6765))
- **mruby-array-ext**: Add `Array#find` and `Array#rfind` methods - **mruby-array-ext**: Add `Array#find` and `Array#rfind` methods
- **mruby-io**: Add `IO#putc` and `Kernel#putc` ([baff6e6](https://github.com/mruby/mruby/commit/baff6e6)) - **mruby-io**: Add `IO#putc` and `Kernel#putc` ([baff6e6](https://github.com/mruby/mruby/commit/baff6e6))
- **mruby-random**: Replace xoshiro with PCG for better memory efficiency ([f1bab01](https://github.com/mruby/mruby/commit/f1bab01)) - **mruby-random**: Replace xoshiro with PCG for better memory efficiency ([f1bab01](https://github.com/mruby/mruby/commit/f1bab01))
@@ -125,6 +131,8 @@ mruby-symbol-ext, mruby-range-ext, mruby-object-ext.
- Optimized masgn to generate literals directly into target registers ([fb5d966](https://github.com/mruby/mruby/commit/fb5d966)) - Optimized masgn to generate literals directly into target registers ([fb5d966](https://github.com/mruby/mruby/commit/fb5d966))
- Optimized splat of literal arrays in args/literals ([1cb8d73](https://github.com/mruby/mruby/commit/1cb8d73)) - Optimized splat of literal arrays in args/literals ([1cb8d73](https://github.com/mruby/mruby/commit/1cb8d73))
- Early termination after too many parse errors ([510ebd7](https://github.com/mruby/mruby/commit/510ebd7)) - Early termination after too many parse errors ([510ebd7](https://github.com/mruby/mruby/commit/510ebd7))
- Chunk array literals at 64 elements to reduce register pressure ([f98d641](https://github.com/mruby/mruby/commit/f98d641))
- Chunk `%w()` and `%i()` literals to reduce register pressure ([62cf0dc](https://github.com/mruby/mruby/commit/62cf0dc))
# VM Optimizations # VM Optimizations
@@ -185,6 +193,10 @@ Other optimizations:
- [#6705](https://github.com/mruby/mruby/issues/6705) Can't get outer class of an object in C - [#6705](https://github.com/mruby/mruby/issues/6705) Can't get outer class of an object in C
- [#6713](https://github.com/mruby/mruby/issues/6713) mruby-polarssl not work - [#6713](https://github.com/mruby/mruby/issues/6713) mruby-polarssl not work
- [#6720](https://github.com/mruby/mruby/issues/6720) Random float range: different behavior from CRuby - [#6720](https://github.com/mruby/mruby/issues/6720) Random float range: different behavior from CRuby
- [#6722](https://github.com/mruby/mruby/issues/6722) RBreak size overflow on 32-bit platforms with MRB_NO_BOXING
- [#6740](https://github.com/mruby/mruby/issues/6740) `%w()`/`%i()` register pressure with large literals
- [#6741](https://github.com/mruby/mruby/issues/6741) `case`/`in` without `else` should raise `NoMatchingPatternError`
- [#6760](https://github.com/mruby/mruby/issues/6760) `mrb_gc_unregister()` not removing all matching entries
# Merged Pull Requests # Merged Pull Requests
@@ -260,6 +272,7 @@ Other optimizations:
- [#6589](https://github.com/mruby/mruby/pull/6589) Add pre-commit hook `check-zip-file-is-not-committed` - [#6589](https://github.com/mruby/mruby/pull/6589) Add pre-commit hook `check-zip-file-is-not-committed`
- [#6591](https://github.com/mruby/mruby/pull/6591) mruby-eval fix license link in README - [#6591](https://github.com/mruby/mruby/pull/6591) mruby-eval fix license link in README
- [#6593](https://github.com/mruby/mruby/pull/6593) README: Add Contributors Avatars, Star History, Table of Contents - [#6593](https://github.com/mruby/mruby/pull/6593) README: Add Contributors Avatars, Star History, Table of Contents
- [#6598](https://github.com/mruby/mruby/pull/6598) Fix heap buffer overflow in `#method_missing`
- [#6599](https://github.com/mruby/mruby/pull/6599) pre-commit: run `markdown-link-check`, `oxipng`, `prettier` manually - [#6599](https://github.com/mruby/mruby/pull/6599) pre-commit: run `markdown-link-check`, `oxipng`, `prettier` manually
- [#6600](https://github.com/mruby/mruby/pull/6600) `dreamcast_shelf build config`: update to use KallistiOS wrappers - [#6600](https://github.com/mruby/mruby/pull/6600) `dreamcast_shelf build config`: update to use KallistiOS wrappers
- [#6601](https://github.com/mruby/mruby/pull/6601) fix: skip local build_config.rb when working in MRUBY_ROOT - [#6601](https://github.com/mruby/mruby/pull/6601) fix: skip local build_config.rb when working in MRUBY_ROOT
@@ -303,6 +316,33 @@ Other optimizations:
- [#6716](https://github.com/mruby/mruby/pull/6716) Fixes identity for proc object - [#6716](https://github.com/mruby/mruby/pull/6716) Fixes identity for proc object
- [#6717](https://github.com/mruby/mruby/pull/6717) Fix mruby-task: wrapping by critical section and setting initial task receiver - [#6717](https://github.com/mruby/mruby/pull/6717) Fix mruby-task: wrapping by critical section and setting initial task receiver
- [#6718](https://github.com/mruby/mruby/pull/6718) Add installation instructions for conda and Homebrew - [#6718](https://github.com/mruby/mruby/pull/6718) Add installation instructions for conda and Homebrew
- [#6723](https://github.com/mruby/mruby/pull/6723) Add `RInteger` and `RFloat` to `RVALUE`
- [#6727](https://github.com/mruby/mruby/pull/6727) Language documentation: update wording of "overloading" section
- [#6729](https://github.com/mruby/mruby/pull/6729) Simplifying dependency addition for gensym task
- [#6730](https://github.com/mruby/mruby/pull/6730) Simplifying presym file generation actions
- [#6733](https://github.com/mruby/mruby/pull/6733) Include `mruby/presym.h` for all source files
- [#6734](https://github.com/mruby/mruby/pull/6734) Chunk array literals at 64 elements to reduce register pressure
- [#6735](https://github.com/mruby/mruby/pull/6735) Prevent full recompilation without changes to presym file
- [#6739](https://github.com/mruby/mruby/pull/6739) Fix MSYS2 build error with drive letters
- [#6743](https://github.com/mruby/mruby/pull/6743) Chunk `%w()` and `%i()` literals to reduce register pressure
- [#6744](https://github.com/mruby/mruby/pull/6744) Raise `NoMatchingPatternError` in `case`/`in` without `else`
- [#6747](https://github.com/mruby/mruby/pull/6747) Correctly handle empty hash as default named argument
- [#6749](https://github.com/mruby/mruby/pull/6749) Fix microcontroller profile
- [#6750](https://github.com/mruby/mruby/pull/6750) Fix out-of-bounds read and divide-by-zero in `Array#product`
- [#6752](https://github.com/mruby/mruby/pull/6752) Fix `attr_reader`-generated methods accepting extra arguments
- [#6753](https://github.com/mruby/mruby/pull/6753) Further optimize `Array#product`
- [#6754](https://github.com/mruby/mruby/pull/6754) Mark `attr_reader` procs as noarg
- [#6755](https://github.com/mruby/mruby/pull/6755) Reload `ci` after `mrb_hash_delete_key()` in keyword argument handling
- [#6756](https://github.com/mruby/mruby/pull/6756) Avoid impact of object modifications caused by `mrb_vm_exec()` calls
- [#6758](https://github.com/mruby/mruby/pull/6758) Don't assign result of `mrb_funcall()` directly to `regs`
- [#6759](https://github.com/mruby/mruby/pull/6759) Define `mrb_bigint_p()` always
- [#6761](https://github.com/mruby/mruby/pull/6761) Fix `mrb_gc_unregister()` to remove all matching entries
- [#6762](https://github.com/mruby/mruby/pull/6762) Write generated test C files atomically to avoid build race condition
- [#6765](https://github.com/mruby/mruby/pull/6765) Fix `Lazy#flat_map` to handle non-enumerable block return values
- [#6767](https://github.com/mruby/mruby/pull/6767) Allow compound statement in parenthesized argument context
- [#6780](https://github.com/mruby/mruby/pull/6780) Fix `String#prepend` with self-referencing arguments
- [#6781](https://github.com/mruby/mruby/pull/6781) Protect `sprintf` format string from mutation during callbacks
- [#6783](https://github.com/mruby/mruby/pull/6783) Pin GitHub Actions workflows to commit hashes
# Security Fixes # Security Fixes
@@ -320,4 +360,8 @@ Other optimizations:
- Heap-use-after-free in insertion_sort ([099d2c47](https://github.com/mruby/mruby/commit/099d2c47)) - Heap-use-after-free in insertion_sort ([099d2c47](https://github.com/mruby/mruby/commit/099d2c47))
- Integer overflow in str_check_length ([6afff1c3](https://github.com/mruby/mruby/commit/6afff1c3)) - Integer overflow in str_check_length ([6afff1c3](https://github.com/mruby/mruby/commit/6afff1c3))
- Integer overflow in Integer#lcm ([070bef24](https://github.com/mruby/mruby/commit/070bef24)) - Integer overflow in Integer#lcm ([070bef24](https://github.com/mruby/mruby/commit/070bef24))
- Heap buffer overflow in `#method_missing` ([550d10a](https://github.com/mruby/mruby/commit/550d10a))
- Out-of-bounds read and divide-by-zero in `Array#product` ([8441eaf](https://github.com/mruby/mruby/commit/8441eaf))
- Heap buffer overflow in `String#prepend` with self-referencing arguments ([18ba026](https://github.com/mruby/mruby/commit/18ba026))
- Use-after-free in `sprintf` via `to_s` callback mutating format string ([48fc422](https://github.com/mruby/mruby/commit/48fc422))
- Multiple memory leak fixes in bigint, Set, Array, and Task gems - Multiple memory leak fixes in bigint, Set, Array, and Task gems
+2
View File
@@ -51,6 +51,8 @@ To get mruby, you can download the stable version 4.0.0 from the official mruby
GitHub repository or clone the trunk of the mruby source tree with the "git GitHub repository or clone the trunk of the mruby source tree with the "git
clone" command. You can also install and compile mruby using [ruby-install](https://github.com/postmodern/ruby-install), [ruby-build](https://github.com/rbenv/ruby-build), [rvm](https://github.com/rvm/rvm), [conda](https://anaconda.org/channels/conda-forge/packages/mruby/overview) or [Homebrew](https://formulae.brew.sh/formula/mruby). clone" command. You can also install and compile mruby using [ruby-install](https://github.com/postmodern/ruby-install), [ruby-build](https://github.com/rbenv/ruby-build), [rvm](https://github.com/rvm/rvm), [conda](https://anaconda.org/channels/conda-forge/packages/mruby/overview) or [Homebrew](https://formulae.brew.sh/formula/mruby).
The release candidate version 4.0.0 of mruby can be downloaded via the following URL: [https://github.com/mruby/mruby/archive/4.0.0-rc3.zip](https://github.com/mruby/mruby/archive/4.0.0-rc3.zip)
The latest development version of mruby can be downloaded via the following URL: [https://github.com/mruby/mruby/zipball/master](https://github.com/mruby/mruby/zipball/master) The latest development version of mruby can be downloaded via the following URL: [https://github.com/mruby/mruby/zipball/master](https://github.com/mruby/mruby/zipball/master)
The trunk of the mruby source tree can be checked out with the The trunk of the mruby source tree can be checked out with the
+2 -4
View File
@@ -292,9 +292,7 @@ class Scene
r = rad.x / nsfs r = rad.x / nsfs
g = rad.y / nsfs g = rad.y / nsfs
b = rad.z / nsfs b = rad.z / nsfs
printf("%c", clamp(r)) print([clamp(r), clamp(g), clamp(b)].pack("CCC"))
printf("%c", clamp(g))
printf("%c", clamp(b))
end end
end end
end end
@@ -303,7 +301,7 @@ end
# File.open("ao.ppm", "w") do |fp| # File.open("ao.ppm", "w") do |fp|
printf("P6\n") printf("P6\n")
printf("%d %d\n", IMAGE_WIDTH, IMAGE_HEIGHT) printf("%d %d\n", IMAGE_WIDTH, IMAGE_HEIGHT)
printf("255\n", IMAGE_WIDTH, IMAGE_HEIGHT) printf("255\n")
Scene.new.render(IMAGE_WIDTH, IMAGE_HEIGHT, NSUBSAMPLES) Scene.new.render(IMAGE_WIDTH, IMAGE_HEIGHT, NSUBSAMPLES)
# Scene.new.render(256, 256, 2) # Scene.new.render(256, 256, 2)
# end # end
+1 -1
View File
@@ -30,7 +30,7 @@ MRuby::CrossBuild.new("ArduinoDue") do |conf|
#configuration for low memory environment #configuration for low memory environment
cc.defines << %w(MRB_HEAP_PAGE_SIZE=64) cc.defines << %w(MRB_HEAP_PAGE_SIZE=64)
cc.defines << %w(KHASH_DEFAULT_SIZE=8) cc.defines << %w(KHASH_INITIAL_SIZE=8)
cc.defines << %w(MRB_GC_STRESS) cc.defines << %w(MRB_GC_STRESS)
#cc.defines << %w(MRB_NO_STDIO) #if you don't need stdio. #cc.defines << %w(MRB_NO_STDIO) #if you don't need stdio.
#cc.defines << %w(POOL_PAGE_SIZE=1000) #effective only for use with mruby-eval #cc.defines << %w(POOL_PAGE_SIZE=1000) #effective only for use with mruby-eval
+1 -1
View File
@@ -16,7 +16,7 @@ MRuby::CrossBuild.new("RX630") do |conf|
#configuration for low memory environment #configuration for low memory environment
cc.defines << %w(MRB_USE_FLOAT32) cc.defines << %w(MRB_USE_FLOAT32)
cc.defines << %w(MRB_HEAP_PAGE_SIZE=64) cc.defines << %w(MRB_HEAP_PAGE_SIZE=64)
cc.defines << %w(KHASH_DEFAULT_SIZE=8) cc.defines << %w(KHASH_INITIAL_SIZE=8)
cc.defines << %w(MRB_GC_STRESS) cc.defines << %w(MRB_GC_STRESS)
cc.defines << %w(MRB_NO_STDIO) #if you don't need stdio. cc.defines << %w(MRB_NO_STDIO) #if you don't need stdio.
#cc.defines << %w(POOL_PAGE_SIZE=1000) #effective only for use with mruby-eval #cc.defines << %w(POOL_PAGE_SIZE=1000) #effective only for use with mruby-eval
+1 -1
View File
@@ -27,7 +27,7 @@ MRuby::CrossBuild.new("chipKITMax32") do |conf|
#configuration for low memory environment #configuration for low memory environment
cc.defines << %w(MRB_HEAP_PAGE_SIZE=64) cc.defines << %w(MRB_HEAP_PAGE_SIZE=64)
cc.defines << %w(KHASH_DEFAULT_SIZE=8) cc.defines << %w(KHASH_INITIAL_SIZE=8)
cc.defines << %w(MRB_GC_STRESS) cc.defines << %w(MRB_GC_STRESS)
#cc.defines << %w(MRB_NO_STDIO) #if you don't need stdio. #cc.defines << %w(MRB_NO_STDIO) #if you don't need stdio.
#cc.defines << %w(POOL_PAGE_SIZE=1000) #effective only for use with mruby-eval #cc.defines << %w(POOL_PAGE_SIZE=1000) #effective only for use with mruby-eval
+2 -4
View File
@@ -64,10 +64,8 @@ MRuby::Build.new do |conf|
# APE binaries use .com extension # APE binaries use .com extension
conf.exts.executable = '.com' conf.exts.executable = '.com'
# Cosmopolitan provides POSIX compatibility, explicitly select POSIX HALs # Cosmopolitan provides POSIX compatibility
conf.gem core: 'hal-posix-io' conf.ports :posix
conf.gem core: 'hal-posix-socket'
conf.gem core: 'hal-posix-dir'
# Standard library # Standard library
conf.gembox 'stdlib' conf.gembox 'stdlib'
+88
View File
@@ -0,0 +1,88 @@
MRuby::Build.new do |conf|
# load specific toolchain settings
conf.toolchain
# Use mrbgems
# conf.gem 'examples/mrbgems/ruby_extension_example'
# conf.gem 'examples/mrbgems/c_extension_example' do |g|
# g.cc.flags << '-g' # append cflags in this gem
# end
# conf.gem 'examples/mrbgems/c_and_ruby_extension_example'
# conf.gem :core => 'mruby-eval'
# conf.gem :mgem => 'mruby-onig-regexp'
# conf.gem :github => 'mattn/mruby-onig-regexp'
# conf.gem :git => 'git@github.com:mattn/mruby-onig-regexp.git', :branch => 'master', :options => '-v'
# include the GEM box
#conf.gembox 'default'
# C compiler settings
# conf.cc do |cc|
# cc.command = ENV['CC'] || 'gcc'
# cc.flags = [ENV['CFLAGS'] || %w()]
# cc.include_paths = ["#{root}/include"]
# cc.defines = %w()
# cc.option_include_path = %q[-I"%s"]
# cc.option_define = '-D%s'
# cc.compile_options = %Q[%{flags} -MMD -o "%{outfile}" -c "%{infile}"]
# end
# mrbc settings
# conf.mrbc do |mrbc|
# mrbc.compile_options = "-g -B%{funcname} -o-" # The -g option is required for line numbers
# end
# Linker settings
# conf.linker do |linker|
# linker.command = ENV['LD'] || 'gcc'
# linker.flags = [ENV['LDFLAGS'] || []]
# linker.flags_before_libraries = []
# linker.libraries = %w()
# linker.flags_after_libraries = []
# linker.library_paths = []
# linker.option_library = '-l%s'
# linker.option_library_path = '-L%s'
# linker.link_options = %Q[%{flags} -o "%{outfile}" %{objs} %{libs}]
# end
# Archiver settings
# conf.archiver do |archiver|
# archiver.command = ENV['AR'] || 'ar'
# archiver.archive_options = 'rs "%{outfile}" %{objs}'
# end
# Parser generator settings
# conf.yacc do |yacc|
# yacc.command = ENV['YACC'] || 'bison'
# yacc.compile_options = %q[-o "%{outfile}" "%{infile}"]
# end
# gperf settings
# conf.gperf do |gperf|
# gperf.command = 'gperf'
# gperf.compile_options = %q[-L ANSI-C -C -j1 -i 1 -o -t -N mrb_reserved_word -k"1,3,$" "%{infile}" > "%{outfile}"]
# end
# file extensions
# conf.exts do |exts|
# exts.object = '.o'
# exts.executable = '' # '.exe' if Windows
# exts.library = '.a'
# end
# file separator
# conf.file_separator = '/'
# change library directory name from the default "lib" if necessary
# conf.libdir_name = 'lib64'
# Turn on `enable_debug` for better debugging
conf.enable_sanitizer 'address,undefined'
conf.enable_debug
conf.enable_bintest
conf.enable_test
conf.ports :glib
conf.cc.defines << 'MRB_TASK_BUILD_DEMO'
conf.gem core: 'mruby-task'
conf.gem core: 'mruby-compiler'
end
+1 -1
View File
@@ -1,4 +1,4 @@
MRuby::Build.new do |conf| MRuby::Build.new('host-cxx') do |conf|
conf.toolchain conf.toolchain
# include the default GEMs # include the default GEMs
+2
View File
@@ -13,6 +13,8 @@ MRuby::Build.new('host') do |conf|
# Generate mruby debugger command (require mruby-eval) # Generate mruby debugger command (require mruby-eval)
conf.gem :core => "mruby-bin-debugger" conf.gem :core => "mruby-bin-debugger"
# Regexp is included via stdlib.gembox
# test # test
conf.enable_test conf.enable_test
# bintest # bintest
+78 -20
View File
@@ -1,36 +1,94 @@
# NOTE: Currently, this configuration file does not support VisualC++! # Build mruby with a shared libmruby.so (in addition to the usual
# Your help is needed! # libmruby.a / executables).
#
# Produces (in build/host/lib/):
# libmruby.a the static archive (as in the default build)
# libmruby.so the shared library, with SONAME=libmruby.so.<MAJOR>.<MINOR>
# libmruby.so.<MAJOR>.<MINOR> symlink to libmruby.so (matches SONAME)
# libmruby.map linker version script (MRUBY_<RELEASE_NO>)
#
# Also produces the matching libmruby_core.so + symlink for completeness.
#
# Symbol versioning ties to MRUBY_RELEASE_NO (e.g. MRUBY_40000 for 4.0.0).
# mruby has historically had ABI breaks between TEENY versions, so the
# version tag uses the full release number rather than just MAJOR.MINOR.
#
# The shared library is built FROM the static archive via
# `-Wl,--whole-archive`, so the existing static-build pipeline (including
# the test infrastructure) is unaffected. Executables in build/host/bin
# remain statically linked; distros that want dynamically-linked
# executables can rebuild them against the .so.
#
# NOTE: gcc/clang only — VisualC++ support requires a separate config.
require "mruby/source"
MRuby::Build.new do |conf| MRuby::Build.new do |conf|
# load specific toolchain settings
conf.toolchain conf.toolchain
# include the GEM box # include the GEM box
conf.gembox 'default' conf.gembox 'default'
# C compiler settings # -fPIC so the static archive's contents can be linked into the .so.
conf.compilers.each do |cc| conf.compilers.each do |cc|
cc.flags << '-fPIC' cc.flags << '-fPIC'
end end
conf.archiver do |archiver|
archiver.command = cc.command
archiver.archive_options = '-shared -o %{outfile} %{objs}'
end
# file extensions
conf.exts do |exts|
exts.library = '.so'
end
# file separator
# conf.file_separator = '/'
# enable this if better compatibility with C++ is desired
#conf.enable_cxx_exception
# Turn on `enable_debug` for better debugging # Turn on `enable_debug` for better debugging
conf.enable_debug conf.enable_debug
conf.enable_bintest conf.enable_bintest
conf.enable_test conf.enable_test
end end
# Add the shared-library targets as a post-build pass, so the default
# static-build pipeline remains untouched.
MRuby.each_target do
next unless name == "host"
libdir = File.join(build_dir, libdir_name)
vermap = File.join(build_dir, "libmruby.map")
vertag = "MRUBY_#{MRuby::Source::MRUBY_RELEASE_NO}"
# Generate the version script eagerly — it has no .o dependencies and is
# tiny enough that lazy generation isn't worth the rake plumbing.
mkdir_p File.dirname(vermap)
File.write(vermap, <<~MAP)
#{vertag} {
global: *;
local: *;
};
MAP
major = MRuby::Source::MRUBY_RELEASE_MAJOR
minor = MRuby::Source::MRUBY_RELEASE_MINOR
[
[libmruby_static, "libmruby"],
[libmruby_core_static, "libmruby_core"],
].each do |archive, basename|
so = File.join(libdir, "#{basename}.so")
symlink = "#{so}.#{major}.#{minor}"
soname = "#{basename}.so.#{major}.#{minor}"
# Build .so from the static archive via --whole-archive.
file so => [archive, vermap] do |t|
_pp "LD", so.relative_path
sh "#{cc.command} -shared -fPIC -o #{so}" \
" -Wl,-soname,#{soname}" \
" -Wl,--version-script=#{vermap}" \
" -Wl,--whole-archive #{archive} -Wl,--no-whole-archive" \
" -lm"
end
products << so
# SONAME-matching symlink: needed so executables linked with -lmruby
# (which embeds the SONAME as DT_NEEDED) can find the library at
# runtime via standard search paths.
file symlink => so do |t|
_pp "LN", "#{symlink.relative_path} -> #{File.basename(so)}"
rm_f symlink
File.symlink(File.basename(so), symlink)
end
products << symlink
end
end
+5 -3
View File
@@ -72,7 +72,9 @@ MRuby::CrossBuild.new("playstationportable") do |conf|
conf.gem :core => "mruby-os-memsize" conf.gem :core => "mruby-os-memsize"
conf.gem :core => "mruby-proc-binding" conf.gem :core => "mruby-proc-binding"
conf.gem :core => "mruby-sleep" conf.gem :core => "mruby-sleep"
conf.gem :core => "mruby-io" # Disabled until PSP-specific HALs are available; the POSIX HALs depend on
conf.gem :core => "mruby-dir" # APIs that the PSP SDK does not fully provide.
#conf.gem :core => "mruby-socket" unsupported # conf.gem :core => "mruby-io"
# conf.gem :core => "mruby-dir"
# conf.gem :core => "mruby-socket"
end end
+30 -30
View File
@@ -4,44 +4,44 @@
New to mruby? Start here: New to mruby? Start here:
| Document | Description | | Document | Description |
| -------- | ----------- | | -------------------------------------------- | -------------------------------------- |
| [Getting Started](guides/getting-started.md) | Build mruby and run your first program | | [Getting Started](guides/getting-started.md) | Build mruby and run your first program |
| [Language Features](guides/language.md) | Ruby subset supported by mruby | | [Language Features](guides/language.md) | Ruby subset supported by mruby |
| [Limitations](limitations.md) | Behavioral differences from CRuby | | [Limitations](limitations.md) | Behavioral differences from CRuby |
## Guides (for embedders and gem authors) ## Guides (for embedders and gem authors)
### Embedding mruby in C ### Embedding mruby in C
| Document | Description | | Document | Description |
| -------- | ----------- | | --------------------------------------- | ------------------------------------------------ |
| [C API Reference](guides/capi.md) | Values, classes, methods, error handling, fibers | | [C API Reference](guides/capi.md) | Values, classes, methods, error handling, fibers |
| [GC Arena](guides/gc-arena-howto.md) | Managing temporary objects in C extensions | | [GC Arena](guides/gc-arena-howto.md) | Managing temporary objects in C extensions |
| [Linking](guides/link.md) | Linking with `libmruby` | | [Linking](guides/link.md) | Linking with `libmruby` |
| [Amalgamation](guides/amalgamation.md) | Single-file build for easy integration | | [Amalgamation](guides/amalgamation.md) | Single-file build for easy integration |
| [Precompiled Symbols](guides/symbol.md) | Compile-time symbol allocation | | [Precompiled Symbols](guides/symbol.md) | Compile-time symbol allocation |
### Building and Configuring ### Building and Configuring
| Document | Description | | Document | Description |
| -------- | ----------- | | ---------------------------------------- | ------------------------------------------- |
| [Compilation](guides/compile.md) | Build system, cross-compilation, toolchains | | [Compilation](guides/compile.md) | Build system, cross-compilation, toolchains |
| [Build Configuration](guides/mrbconf.md) | Compile-time macros (`MRB_*` flags) | | [Build Configuration](guides/mrbconf.md) | Compile-time macros (`MRB_*` flags) |
| [mrbgems](guides/mrbgems.md) | Creating and managing gems | | [mrbgems](guides/mrbgems.md) | Creating and managing gems |
| [Memory](guides/memory.md) | Allocator customization and heap regions | | [Memory](guides/memory.md) | Allocator customization and heap regions |
### Tools ### Tools
| Document | Description | | Document | Description |
| -------- | ----------- | | ----------------------------------------------- | ----------------------------------------------- |
| [Debugger](guides/debugger.md) | Using `mrdb` for debugging | | [Debugger](guides/debugger.md) | Using `mrdb` for debugging |
| [ROM Method Tables](guides/rom-method-table.md) | Read-only method tables for constrained devices | | [ROM Method Tables](guides/rom-method-table.md) | Read-only method tables for constrained devices |
### Reference ### Reference
| Document | Description | | Document | Description |
| -------- | ----------- | | ------------------------------------- | ------------------ |
| [Directory Structure](guides/hier.md) | Source tree layout | | [Directory Structure](guides/hier.md) | Source tree layout |
## Internals (for mruby contributors) ## Internals (for mruby contributors)
@@ -49,14 +49,14 @@ New to mruby? Start here:
Start with [Architecture](internal/architecture.md) for an overview, Start with [Architecture](internal/architecture.md) for an overview,
then dive into the subsystem you need: then dive into the subsystem you need:
| Document | Description | | Document | Description |
| -------- | ----------- | | ----------------------------------------- | -------------------------------------------------- |
| [Architecture](internal/architecture.md) | Overview of object model, VM, GC, compiler | | [Architecture](internal/architecture.md) | Overview of object model, VM, GC, compiler |
| [Virtual Machine](internal/vm.md) | Dispatch loop, call frames, method lookup, fibers | | [Virtual Machine](internal/vm.md) | Dispatch loop, call frames, method lookup, fibers |
| [Garbage Collector](internal/gc.md) | Tri-color marking, write barriers, generational GC | | [Garbage Collector](internal/gc.md) | Tri-color marking, write barriers, generational GC |
| [Compiler Pipeline](internal/compiler.md) | Parser, code generator, IRep, binary format | | [Compiler Pipeline](internal/compiler.md) | Parser, code generator, IRep, binary format |
| [Opcodes](internal/opcode.md) | VM instruction set reference | | [Opcodes](internal/opcode.md) | VM instruction set reference |
| [Value Boxing](internal/boxing.md) | How `mrb_value` encodes types | | [Value Boxing](internal/boxing.md) | How `mrb_value` encodes types |
## Release Notes ## Release Notes
+3 -3
View File
@@ -73,14 +73,14 @@ The following gems work with amalgamation:
- `mruby-enum-ext`, `mruby-compar-ext` - `mruby-enum-ext`, `mruby-compar-ext`
- `mruby-error`, `mruby-math`, `mruby-struct` - `mruby-error`, `mruby-math`, `mruby-struct`
- `mruby-bigint`, `mruby-rational`, `mruby-complex` - `mruby-bigint`, `mruby-rational`, `mruby-complex`
- `mruby-io` (with `hal-posix-io`) - `mruby-io` (with the active `ports/<name>/` HAL)
- `mruby-task` (with `hal-posix-task`) - `mruby-task` (with the active `ports/<name>/` HAL)
### Platform-Dependent Gems ### Platform-Dependent Gems
Gems that use a HAL (Hardware Abstraction Layer) include Gems that use a HAL (Hardware Abstraction Layer) include
platform-specific code in the amalgamation. For example, if platform-specific code in the amalgamation. For example, if
`mruby-io` selects `hal-posix-io` on Linux, the generated `mruby.c` `mruby-io` selects its POSIX port on Linux, the generated `mruby.c`
contains POSIX-specific code and cannot be compiled on Windows. contains POSIX-specific code and cannot be compiled on Windows.
If you need amalgamated files for multiple platforms, generate them If you need amalgamated files for multiple platforms, generate them
+7
View File
@@ -381,6 +381,13 @@ mrb_define_method(mrb, point, "initialize", point_init, MRB_ARGS_REQ(2));
mrb_define_method(mrb, point, "x", point_x, MRB_ARGS_NONE()); mrb_define_method(mrb, point, "x", point_x, MRB_ARGS_NONE());
``` ```
**Do not call into mruby from a `dfree` handler.** The handler runs
from inside GC sweep; allocating Ruby objects, calling
`mrb_funcall`, `mrb_yield`, raising exceptions, or otherwise
re-entering the VM can trigger a recursive GC that revisits the
same object and causes double-free. Keep `dfree` to `mrb_free` /
plain C cleanup of the wrapped data only.
## Exception Handling ## Exception Handling
### Raising Exceptions ### Raising Exceptions
+1 -1
View File
@@ -40,7 +40,7 @@ To confirm mrdb was installed properly, run mrdb with the `--version` option:
```bash ```bash
$ mrdb --version $ mrdb --version
mruby 3.4.0 (2025-04-20) mruby 4.0.0 (2026-04-20)
``` ```
## 2.2 Basic Operation ## 2.2 Basic Operation
+87 -87
View File
@@ -205,11 +205,11 @@ mruby's numeric type sizes depend on the boxing mode and platform.
### Integer ### Integer
| Configuration | Range | | Configuration | Range |
| ------------- | ----- | | -------------------------------------- | ---------------- |
| 64-bit word boxing (default on 64-bit) | roughly +/- 2^62 | | 64-bit word boxing (default on 64-bit) | roughly +/- 2^62 |
| 32-bit word boxing (default on 32-bit) | roughly +/- 2^30 | | 32-bit word boxing (default on 32-bit) | roughly +/- 2^30 |
| NaN boxing (64-bit only) | -2^31 to 2^31-1 | | NaN boxing (64-bit only) | -2^31 to 2^31-1 |
Integer overflow raises a `RangeError` unless the `mruby-bigint` gem Integer overflow raises a `RangeError` unless the `mruby-bigint` gem
is included, in which case integers automatically promote to is included, in which case integers automatically promote to
@@ -235,32 +235,32 @@ With word boxing on 64-bit, many float values are stored inline
These classes are always available in mruby (no gem required): These classes are always available in mruby (no gem required):
| Class | Notes | | Class | Notes |
| ----- | ----- | | ------------- | -------------------------------------- |
| Object | Base class for all objects | | Object | Base class for all objects |
| Module | Module definition and mixin | | Module | Module definition and mixin |
| Class | Class definition and instantiation | | Class | Class definition and instantiation |
| NilClass | Singleton `nil` | | NilClass | Singleton `nil` |
| TrueClass | Singleton `true` | | TrueClass | Singleton `true` |
| FalseClass | Singleton `false` | | FalseClass | Singleton `false` |
| Integer | Fixed-precision integer | | Integer | Fixed-precision integer |
| Float | Floating-point (unless `MRB_NO_FLOAT`) | | Float | Floating-point (unless `MRB_NO_FLOAT`) |
| Symbol | Interned identifier | | Symbol | Interned identifier |
| String | Mutable byte string | | String | Mutable byte string |
| Array | Ordered collection | | Array | Ordered collection |
| Hash | Key-value mapping | | Hash | Key-value mapping |
| Range | Interval representation | | Range | Interval representation |
| Proc | Closure / callable object | | Proc | Closure / callable object |
| Exception | Exception hierarchy root | | Exception | Exception hierarchy root |
| StandardError | Common error base | | StandardError | Common error base |
### Core Modules ### Core Modules
| Module | Notes | | Module | Notes |
| ------ | ----- | | ---------- | ----------------------------------------- |
| Kernel | Core methods (`puts`, `p`, `raise`, etc.) | | Kernel | Core methods (`puts`, `p`, `raise`, etc.) |
| Comparable | Comparison operators via `<=>` | | Comparable | Comparison operators via `<=>` |
| Enumerable | Collection iteration methods | | Enumerable | Collection iteration methods |
## Standard Library (via gemboxes) ## Standard Library (via gemboxes)
@@ -270,70 +270,70 @@ gembox provides the class or feature you need:
### Classes and Modules ### Classes and Modules
| Class/Module | Gembox | Gem | | Class/Module | Gembox | Gem |
| ------------ | ------ | --- | | --------------------- | ---------- | ----------------- |
| Fiber | stdlib | mruby-fiber | | Fiber | stdlib | mruby-fiber |
| Enumerator | stdlib | mruby-enumerator | | Enumerator | stdlib | mruby-enumerator |
| Enumerator::Lazy | stdlib | mruby-enum-lazy | | Enumerator::Lazy | stdlib | mruby-enum-lazy |
| Set | stdlib | mruby-set | | Set | stdlib | mruby-set |
| ObjectSpace | stdlib | mruby-objectspace | | ObjectSpace | stdlib | mruby-objectspace |
| Time | stdlib-ext | mruby-time | | Time | stdlib-ext | mruby-time |
| Struct | stdlib-ext | mruby-struct | | Struct | stdlib-ext | mruby-struct |
| Data | stdlib-ext | mruby-data | | Data | stdlib-ext | mruby-data |
| Random | stdlib-ext | mruby-random | | Random | stdlib-ext | mruby-random |
| IO, File | stdlib-io | mruby-io | | IO, File | stdlib-io | mruby-io |
| Socket | stdlib-io | mruby-socket | | Socket | stdlib-io | mruby-socket |
| Dir | stdlib-io | mruby-dir | | Dir | stdlib-io | mruby-dir |
| Errno | stdlib-io | mruby-errno | | Errno | stdlib-io | mruby-errno |
| Math | math | mruby-math | | Math | math | mruby-math |
| Rational | math | mruby-rational | | Rational | math | mruby-rational |
| Complex | math | mruby-complex | | Complex | math | mruby-complex |
| Bigint | math | mruby-bigint | | Bigint | math | mruby-bigint |
| Method, UnboundMethod | metaprog | mruby-method | | Method, UnboundMethod | metaprog | mruby-method |
### Methods and Features ### Methods and Features
| Feature | Gembox | Gem | | Feature | Gembox | Gem |
| ------- | ------ | --- | | ----------------------------- | ---------- | ------------------ |
| `catch`/`throw` | stdlib | mruby-catch | | `catch`/`throw` | stdlib | mruby-catch |
| `Kernel#sprintf`, `String#%` | stdlib-ext | mruby-sprintf | | `Kernel#sprintf`, `String#%` | stdlib-ext | mruby-sprintf |
| `Array#pack`, `String#unpack` | stdlib-ext | mruby-pack | | `Array#pack`, `String#unpack` | stdlib-ext | mruby-pack |
| `Kernel#rand` | stdlib-ext | mruby-random | | `Kernel#rand` | stdlib-ext | mruby-random |
| `Kernel#eval` | metaprog | mruby-eval | | `Kernel#eval` | metaprog | mruby-eval |
| `Kernel#binding` | metaprog | mruby-binding | | `Kernel#binding` | metaprog | mruby-binding |
| `Proc#binding` | metaprog | mruby-proc-binding | | `Proc#binding` | metaprog | mruby-proc-binding |
| Runtime compiler | metaprog | mruby-compiler | | Runtime compiler | metaprog | mruby-compiler |
### Core Class Extensions ### Core Class Extensions
The `stdlib` gembox also extends built-in classes with additional The `stdlib` gembox also extends built-in classes with additional
methods. These are included by default: methods. These are included by default:
| Extension | Examples | | Extension | Examples |
| --------- | -------- | | ----------------------- | ---------------------------------------------- |
| Array extensions | `#dig`, `#union`, `#difference` | | Array extensions | `#dig`, `#union`, `#difference` |
| Hash extensions | `#dig`, `#transform_keys`, `#transform_values` | | Hash extensions | `#dig`, `#transform_keys`, `#transform_values` |
| String extensions | `#encode`, `#bytes`, `#chars` | | String extensions | `#encode`, `#bytes`, `#chars` |
| Numeric extensions | `Integer#digits`, `Integer#pow` | | Numeric extensions | `Integer#digits`, `Integer#pow` |
| Comparable extensions | `#clamp` | | Comparable extensions | `#clamp` |
| Enumerable extensions | `#sort_by`, `#min_by`, `#max_by`, `#tally` | | Enumerable extensions | `#sort_by`, `#min_by`, `#max_by`, `#tally` |
| Range extensions | `#size`, `#cover?` | | Range extensions | `#size`, `#cover?` |
| Proc extensions | `#<<`, `#>>` (composition) | | Proc extensions | `#<<`, `#>>` (composition) |
| Symbol extensions | `#to_proc` | | Symbol extensions | `#to_proc` |
| Object extensions | `#then`, `#yield_self` | | Object extensions | `#then`, `#yield_self` |
| Kernel extensions | `#__method__` | | Kernel extensions | `#__method__` |
| Class/Module extensions | `Module#name` | | Class/Module extensions | `Module#name` |
### Gembox Summary ### Gembox Summary
| Gembox | Contents | Notes | | Gembox | Contents | Notes |
| ------ | -------- | ----- | | ------------ | --------------------------------------------- | -------------------------------------------- |
| `stdlib` | Core class extensions, Fiber, Enumerator, Set | Works with `MRB_NO_STDIO` and `MRB_NO_FLOAT` | | `stdlib` | Core class extensions, Fiber, Enumerator, Set | Works with `MRB_NO_STDIO` and `MRB_NO_FLOAT` |
| `stdlib-ext` | Time, Struct, Data, Random, sprintf, pack | Works with `MRB_NO_STDIO` and `MRB_NO_FLOAT` | | `stdlib-ext` | Time, Struct, Data, Random, sprintf, pack | Works with `MRB_NO_STDIO` and `MRB_NO_FLOAT` |
| `stdlib-io` | IO, File, Dir, Socket, Errno | Requires stdio | | `stdlib-io` | IO, File, Dir, Socket, Errno | Requires stdio |
| `math` | Math, Rational, Complex, Bigint | Works with `MRB_NO_STDIO` | | `math` | Math, Rational, Complex, Bigint | Works with `MRB_NO_STDIO` |
| `metaprog` | eval, binding, Method, compiler | Works with `MRB_NO_STDIO` and `MRB_NO_FLOAT` | | `metaprog` | eval, binding, Method, compiler | Works with `MRB_NO_STDIO` and `MRB_NO_FLOAT` |
| `default` | All of the above + CLI tools | Full installation | | `default` | All of the above + CLI tools | Full installation |
## Key Differences from CRuby ## Key Differences from CRuby
@@ -416,15 +416,15 @@ differently on 32-bit or NaN boxing configurations.
Key compile-time macros that affect language behavior: Key compile-time macros that affect language behavior:
| Macro | Effect | | Macro | Effect |
| ----- | ------ | | -------------------- | ---------------------------------- |
| `MRB_NO_FLOAT` | Remove all float support | | `MRB_NO_FLOAT` | Remove all float support |
| `MRB_USE_FLOAT32` | Use 32-bit float instead of double | | `MRB_USE_FLOAT32` | Use 32-bit float instead of double |
| `MRB_UTF8_STRING` | Enable UTF-8 string handling | | `MRB_UTF8_STRING` | Enable UTF-8 string handling |
| `MRB_INT32` | Force 32-bit integer | | `MRB_INT32` | Force 32-bit integer |
| `MRB_INT64` | Force 64-bit integer | | `MRB_INT64` | Force 64-bit integer |
| `MRB_STR_LENGTH_MAX` | Max string length (default 1MB) | | `MRB_STR_LENGTH_MAX` | Max string length (default 1MB) |
| `MRB_ARY_LENGTH_MAX` | Max array length (default 2^17) | | `MRB_ARY_LENGTH_MAX` | Max array length (default 2^17) |
See [mrbconf.md](mrbconf.md) for the complete list of configuration See [mrbconf.md](mrbconf.md) for the complete list of configuration
macros. macros.
+4 -4
View File
@@ -202,10 +202,10 @@ any other heap page. The only differences are:
The page size is controlled by `MRB_HEAP_PAGE_SIZE` (default: 1024 slots). The page size is controlled by `MRB_HEAP_PAGE_SIZE` (default: 1024 slots).
Each page occupies: Each page occupies:
| Platform | Slot size | Page size (approx) | | Platform | Slot size | Page size (approx) |
|----------|-----------|---------------------| | -------- | --------- | ------------------ |
| 64-bit | 40 bytes | ~41 KB | | 64-bit | 40 bytes | ~41 KB |
| 32-bit | 24 bytes | ~25 KB | | 32-bit | 24 bytes | ~25 KB |
To estimate pages for a given buffer: `pages = buffer_size / sizeof(mrb_heap_page)`. To estimate pages for a given buffer: `pages = buffer_size / sizeof(mrb_heap_page)`.
Each page provides `MRB_HEAP_PAGE_SIZE` object slots. Each page provides `MRB_HEAP_PAGE_SIZE` object slots.
+3 -3
View File
@@ -233,10 +233,10 @@ end
- Specifies 4th argument(`argc`) max value of `mrb_funcall`. - Specifies 4th argument(`argc`) max value of `mrb_funcall`.
- Raises `ArgumentError` when the `argc` argument is bigger then this value `mrb_funcall`. - Raises `ArgumentError` when the `argc` argument is bigger then this value `mrb_funcall`.
`KHASH_DEFAULT_SIZE` `KHASH_INITIAL_SIZE`
- Default value is `32`. - Default value is `32`.
- Specifies default size of khash table bucket. - Specifies initial size of khash table bucket.
- Used in `kh_init_ ## name` function. - Used in `kh_init_ ## name` function.
`MRB_NO_METHOD_CACHE` `MRB_NO_METHOD_CACHE`
@@ -272,7 +272,7 @@ deployment targets. Define one of the following:
`MRB_CONSTRAINED_BASELINE_PROFILE` `MRB_CONSTRAINED_BASELINE_PROFILE`
- For micro controllers. - For micro controllers.
- Enables `MRB_NO_METHOD_CACHE`, reduces `KHASH_DEFAULT_SIZE` to `16`, - Enables `MRB_NO_METHOD_CACHE`, reduces `KHASH_INITIAL_SIZE` to `16`,
and `MRB_HEAP_PAGE_SIZE` to `256`. and `MRB_HEAP_PAGE_SIZE` to `256`.
`MRB_BASELINE_PROFILE` `MRB_BASELINE_PROFILE`
+62
View File
@@ -172,6 +172,8 @@ The maximal GEM structure looks like this:
| |
+- src/ <- Source for C extension +- src/ <- Source for C extension
| |
+- ports/<name>/ <- Platform-specific C sources (see Platform Ports)
|
+- tools/ <- Source for Executable (in C) +- tools/ <- Source for Executable (in C)
| |
+- test/ <- Test code (Ruby) +- test/ <- Test code (Ruby)
@@ -182,6 +184,8 @@ contains C/C++ files to extend mruby. The `include` directory contains C/C++ hea
files. The `test` directory contains C/C++ and pure Ruby files for testing purposes files. The `test` directory contains C/C++ and pure Ruby files for testing purposes
which will be used by `mrbtest`. `mrbgem.rake` contains the specification which will be used by `mrbtest`. `mrbgem.rake` contains the specification
to compile C and Ruby files. `README.md` is a short description of your GEM. to compile C and Ruby files. `README.md` is a short description of your GEM.
The optional `ports/<name>/` directories hold platform-specific C sources
selected at build time; see [Platform Ports](#platform-ports-ports) below.
## Build process ## Build process
@@ -332,6 +336,64 @@ end
**NOTE**: Using the `build_settings` method will cause GEM's all build command settings **NOTE**: Using the `build_settings` method will cause GEM's all build command settings
directly written in the block passed to `MRuby::Gem::Specification.new` to be ignored. directly written in the block passed to `MRuby::Gem::Specification.new` to be ignored.
## Platform Ports (ports/)
A gem may ship platform-specific C sources under `ports/<name>/`
subdirectories. The build configuration selects which port name(s)
are active via `conf.ports`, and each gem compiles the sources of
the first matching `ports/<name>/` it ships:
```ruby
MRuby::Build.new do |conf|
conf.toolchain
conf.ports :posix # selects ports/posix/ across all gems
end
```
`conf.ports` accepts multiple names as a fallback chain. Each gem
picks the first directory in the list that exists on its side:
```ruby
conf.ports :rp2040, :posix # try rp2040 per-gem, else posix
```
Host builds auto-detect `:posix` or `:win` when `conf.ports` is
not set. Sources outside `ports/` (i.e. `src/`) are always
compiled regardless of the port selection.
### External HAL Provider Gems
A third-party gem may replace another gem's bundled port at build
time. A gem whose name matches `hal-<short>-<conf>` is recognized
as the external HAL provider for the target gem whose name's last
`-`-separated segment is `<short>`. For example, `hal-task-glib`
overrides the HAL of `mruby-task`; `hal-io-uring` would override
`mruby-io`. The HAL provider must depend on its target so it can
`#include` the target's HAL header:
```ruby
MRuby::Gem::Specification.new('hal-task-glib') do |spec|
spec.license = 'MIT'
spec.author = 'Your Name'
spec.summary = 'GLib HAL for mruby-task'
spec.add_dependency 'mruby-task', core: 'mruby-task'
# src/ contains the HAL implementation
end
```
When a matching HAL provider gem is present in the build, the
target gem's `ports/<conf.ports>/` sources are dropped from the
build automatically. The HAL provider's own sources supply the
implementation instead, avoiding duplicate symbol errors at link
time. Loading two gems that match the same `hal-<short>-*`
prefix is a build error.
The naming convention is the only signal -- no spec attribute,
no `add_dependency` flag is required. A gem author who wants to
contribute an additional bundled port upstream sends a PR adding
`<target-gem>/ports/<name>/`; a gem author who prefers to ship
out of tree publishes a `hal-<short>-<conf>` gem instead.
## C Extension ## C Extension
mruby can be extended with C. This is possible by using the C API to mruby can be extended with C. This is possible by using the C API to
+6 -3
View File
@@ -117,8 +117,8 @@ Defined in `include/mruby/class.h`:
```c ```c
union mrb_mt_ptr { union mrb_mt_ptr {
mrb_func_t func; /* first member: see MRB_MT_ENTRY note */
const struct RProc *proc; const struct RProc *proc;
mrb_func_t func;
}; };
typedef struct mrb_mt_entry { typedef struct mrb_mt_entry {
@@ -139,9 +139,12 @@ typedef struct mrb_mt_tbl {
```c ```c
/* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with /* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with
MRB_MT_PRIVATE. The macro OR's in MRB_MT_FUNC automatically. */ MRB_MT_PRIVATE. The macro OR's in MRB_MT_FUNC automatically.
`func` must be the first member of `union mrb_mt_ptr` so that
positional initialization works on legacy C++ compilers that do
not accept C99 designated initializers. */
#define MRB_MT_ENTRY(fn, sym, flags) \ #define MRB_MT_ENTRY(fn, sym, flags) \
{ { .func = (fn) }, (sym), (flags) | MRB_MT_FUNC } { { (fn) }, (sym), (flags) | MRB_MT_FUNC }
/* Extract aspec from combined flags */ /* Extract aspec from combined flags */
#define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff)) #define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff))
+51 -51
View File
@@ -31,17 +31,17 @@ struct RBasic (8 bytes on 64-bit)
All object structs embed this header via `MRB_OBJECT_HEADER`: All object structs embed this header via `MRB_OBJECT_HEADER`:
| Struct | Ruby Type | Extra Fields | | Struct | Ruby Type | Extra Fields |
| ------ | --------- | ------------ | | ------------ | ---------------- | ---------------------------------- |
| `RObject` | Object instances | `iv` (instance variables) | | `RObject` | Object instances | `iv` (instance variables) |
| `RClass` | Class/Module | `iv`, `mt` (method table), `super` | | `RClass` | Class/Module | `iv`, `mt` (method table), `super` |
| `RString` | String | embedded or heap buffer, length | | `RString` | String | embedded or heap buffer, length |
| `RArray` | Array | embedded or heap buffer, length | | `RArray` | Array | embedded or heap buffer, length |
| `RHash` | Hash | hash table or k-v array | | `RHash` | Hash | hash table or k-v array |
| `RProc` | Proc/Lambda | `irep` or C function, environment | | `RProc` | Proc/Lambda | `irep` or C function, environment |
| `RData` | C data wrapper | `void *data`, `mrb_data_type` | | `RData` | C data wrapper | `void *data`, `mrb_data_type` |
| `RFiber` | Fiber | `mrb_context` | | `RFiber` | Fiber | `mrb_context` |
| `RException` | Exception | `iv` | | `RException` | Exception | `iv` |
Immediate values (Integer, Symbol, `true`, `false`, `nil`) are encoded Immediate values (Integer, Symbol, `true`, `false`, `nil`) are encoded
directly in `mrb_value` without heap allocation. The encoding depends on directly in `mrb_value` without heap allocation. The encoding depends on
@@ -111,52 +111,52 @@ See [compiler.md](compiler.md) for detailed compiler internals,
### Core (`src/`) ### Core (`src/`)
| File | Responsibility | | File | Responsibility |
| ---- | -------------- | | ------------- | ---------------------------------------------- |
| `vm.c` | Bytecode dispatch loop, method invocation | | `vm.c` | Bytecode dispatch loop, method invocation |
| `state.c` | `mrb_state` init/close, irep management | | `state.c` | `mrb_state` init/close, irep management |
| `gc.c` | Garbage collector (mark-sweep, incremental) | | `gc.c` | Garbage collector (mark-sweep, incremental) |
| `class.c` | Class/module definition, method tables | | `class.c` | Class/module definition, method tables |
| `object.c` | Core object operations | | `object.c` | Core object operations |
| `variable.c` | Instance/class/global variables, object shapes | | `variable.c` | Instance/class/global variables, object shapes |
| `proc.c` | Proc/Lambda/closure handling | | `proc.c` | Proc/Lambda/closure handling |
| `array.c` | Array implementation | | `array.c` | Array implementation |
| `string.c` | String implementation (embedded, shared, heap) | | `string.c` | String implementation (embedded, shared, heap) |
| `hash.c` | Hash implementation (open addressing) | | `hash.c` | Hash implementation (open addressing) |
| `numeric.c` | Integer/Float arithmetic | | `numeric.c` | Integer/Float arithmetic |
| `symbol.c` | Symbol table and interning | | `symbol.c` | Symbol table and interning |
| `range.c` | Range implementation | | `range.c` | Range implementation |
| `error.c` | Exception creation, raise, backtrace | | `error.c` | Exception creation, raise, backtrace |
| `kernel.c` | Kernel module methods | | `kernel.c` | Kernel module methods |
| `load.c` | `.mrb` bytecode loading | | `load.c` | `.mrb` bytecode loading |
| `dump.c` | Bytecode serialization (write `.mrb`) | | `dump.c` | Bytecode serialization (write `.mrb`) |
| `print.c` | Print/puts/p output | | `print.c` | Print/puts/p output |
| `backtrace.c` | Stack trace generation | | `backtrace.c` | Stack trace generation |
### Compiler (`mrbgems/mruby-compiler/core/`) ### Compiler (`mrbgems/mruby-compiler/core/`)
| File | Responsibility | | File | Responsibility |
| ---- | -------------- | | ----------- | ------------------------------- |
| `parse.y` | Yacc grammar → AST | | `parse.y` | Yacc grammar → AST |
| `y.tab.c` | Generated parser (from parse.y) | | `y.tab.c` | Generated parser (from parse.y) |
| `codegen.c` | AST → bytecode (irep) | | `codegen.c` | AST → bytecode (irep) |
| `node.h` | AST node type definitions | | `node.h` | AST node type definitions |
### Key Headers (`include/mruby/`) ### Key Headers (`include/mruby/`)
| Header | Contents | | Header | Contents |
| ------ | -------- | | ------------ | ------------------------------------- |
| `mruby.h` | `mrb_state`, core API declarations | | `mruby.h` | `mrb_state`, core API declarations |
| `value.h` | `mrb_value`, type enums, value macros | | `value.h` | `mrb_value`, type enums, value macros |
| `object.h` | `RBasic`, `RObject`, object header | | `object.h` | `RBasic`, `RObject`, object header |
| `class.h` | `RClass`, method table types | | `class.h` | `RClass`, method table types |
| `string.h` | `RString`, string macros | | `string.h` | `RString`, string macros |
| `array.h` | `RArray`, array macros | | `array.h` | `RArray`, array macros |
| `hash.h` | `RHash`, hash API | | `hash.h` | `RHash`, hash API |
| `data.h` | `RData`, C data wrapping | | `data.h` | `RData`, C data wrapping |
| `irep.h` | `mrb_irep`, bytecode structures | | `irep.h` | `mrb_irep`, bytecode structures |
| `compile.h` | Compiler context, `mrb_load_string` | | `compile.h` | Compiler context, `mrb_load_string` |
| `boxing_*.h` | Value boxing implementations | | `boxing_*.h` | Value boxing implementations |
## mrbgems System ## mrbgems System
+10 -10
View File
@@ -77,16 +77,16 @@ No boxing represents `mrb_value` by the C struct with `type` and the value union
## Comparison ## Comparison
| Property | Word Boxing | NaN Boxing | No Boxing | | Property | Word Boxing | NaN Boxing | No Boxing |
| ---------------------- | ------------------ | ------------------ | -------------------- | | ---------------------- | ----------------- | ---------------- | -------------------- |
| `mrb_value` size | 1 word (4/8 byte) | 8 bytes | 2 words (8/16 bytes) | | `mrb_value` size | 1 word (4/8 byte) | 8 bytes | 2 words (8/16 bytes) |
| Default on | most platforms | (manual opt-in) | `host-debug` | | Default on | most platforms | (manual opt-in) | `host-debug` |
| Macro | `MRB_WORD_BOXING` | `MRB_NAN_BOXING` | `MRB_NO_BOXING` | | Macro | `MRB_WORD_BOXING` | `MRB_NAN_BOXING` | `MRB_NO_BOXING` |
| Inline integers | yes (31/63 bit) | yes (32 bit) | yes (full width) | | Inline integers | yes (31/63 bit) | yes (32 bit) | yes (full width) |
| Inline floats (64-bit) | yes (rotation) | yes (native) | yes (struct field) | | Inline floats (64-bit) | yes (rotation) | yes (native) | yes (struct field) |
| Inline floats (32-bit) | no (heap RFloat) | yes (native) | yes (struct field) | | Inline floats (32-bit) | no (heap RFloat) | yes (native) | yes (struct field) |
| Pointer size limit | none | 48 bits | none | | Pointer size limit | none | 48 bits | none |
| Debugger friendly | no | no | yes | | Debugger friendly | no | no | yes |
## ABI Compatibility ## ABI Compatibility
+59 -59
View File
@@ -40,18 +40,18 @@ file: `mrbgems/mruby-compiler/core/parse.y`.
The parser maintains extensive state in `mrb_parser_state`: The parser maintains extensive state in `mrb_parser_state`:
- **lstate**: current lexer state (EXPR\_BEG, EXPR\_END, EXPR\_ARG, - **lstate**: current lexer state (EXPR_BEG, EXPR_END, EXPR_ARG,
EXPR\_DOT, EXPR\_FNAME, etc.). Controls how tokens like `+`/`-` EXPR_DOT, EXPR_FNAME, etc.). Controls how tokens like `+`/`-`
are interpreted (sign vs operator) and whether newlines are are interpreted (sign vs operator) and whether newlines are
significant. significant.
- **locals**: stack of local variable lists (one per scope), stored - **locals**: stack of local variable lists (one per scope), stored
as cons-lists of symbols. as cons-lists of symbols.
- **lex\_strterm**: string/heredoc parsing state for handling nested - **lex_strterm**: string/heredoc parsing state for handling nested
interpolation. interpolation.
- **cond\_stack**, **cmdarg\_stack**: bit stacks tracking - **cond_stack**, **cmdarg_stack**: bit stacks tracking
conditional and command argument contexts. conditional and command argument contexts.
- **tree**: root AST node after successful parse. - **tree**: root AST node after successful parse.
- **error\_buffer**: accumulated parse errors. - **error_buffer**: accumulated parse errors.
### AST Nodes ### AST Nodes
@@ -123,10 +123,10 @@ reuse temporaries within an expression.
Instructions are emitted via helper functions: Instructions are emitted via helper functions:
- `genop_0(opcode)`: no operands - `genop_0(opcode)`: no operands
- `genop_1(opcode, a)`: one operand (auto-extends with OP\_EXT1 - `genop_1(opcode, a)`: one operand (auto-extends with OP_EXT1
if a > 255) if a > 255)
- `genop_2(opcode, a, b)`: two operands (auto-extends with - `genop_2(opcode, a, b)`: two operands (auto-extends with
OP\_EXT1/2/3 as needed) OP_EXT1/2/3 as needed)
- `genop_3(opcode, a, b, c)`: three operands - `genop_3(opcode, a, b, c)`: three operands
- `genop_W(opcode, a)`: 24-bit operand - `genop_W(opcode, a)`: 24-bit operand
- `genop_2S(opcode, a, b)`: one 8-bit + one 16-bit operand - `genop_2S(opcode, a, b)`: one 8-bit + one 16-bit operand
@@ -176,14 +176,14 @@ mrb_irep
Pool entries store constants referenced by instructions: Pool entries store constants referenced by instructions:
| Type | Tag | Description | | Type | Tag | Description |
| ---- | --- | ----------- | | ---------------- | --- | ------------------------------- |
| `IREP_TT_STR` | 0 | Dynamic string (heap allocated) | | `IREP_TT_STR` | 0 | Dynamic string (heap allocated) |
| `IREP_TT_SSTR` | 2 | Static string (read-only) | | `IREP_TT_SSTR` | 2 | Static string (read-only) |
| `IREP_TT_INT32` | 1 | 32-bit integer | | `IREP_TT_INT32` | 1 | 32-bit integer |
| `IREP_TT_INT64` | 3 | 64-bit integer | | `IREP_TT_INT64` | 3 | 64-bit integer |
| `IREP_TT_FLOAT` | 5 | Floating-point number | | `IREP_TT_FLOAT` | 5 | Floating-point number |
| `IREP_TT_BIGINT` | 7 | Arbitrary-precision integer | | `IREP_TT_BIGINT` | 7 | Arbitrary-precision integer |
The code generator deduplicates pool entries: identical strings The code generator deduplicates pool entries: identical strings
and equal numeric values share the same pool index. and equal numeric values share the same pool index.
@@ -209,28 +209,28 @@ During exception unwinding, handlers are searched in reverse order
Standard instructions use 8-bit operands. When a value exceeds Standard instructions use 8-bit operands. When a value exceeds
255, extension prefixes widen operands to 16 bits: 255, extension prefixes widen operands to 16 bits:
| Prefix | Effect | | Prefix | Effect |
| ------ | ------ | | --------- | --------------------------------- |
| `OP_EXT1` | First operand (a) becomes 16-bit | | `OP_EXT1` | First operand (a) becomes 16-bit |
| `OP_EXT2` | Second operand (b) becomes 16-bit | | `OP_EXT2` | Second operand (b) becomes 16-bit |
| `OP_EXT3` | Both a and b become 16-bit | | `OP_EXT3` | Both a and b become 16-bit |
Instruction formats: Instruction formats:
| Format | Layout | Size | | Format | Layout | Size |
| ------ | ------ | ---- | | ------ | ----------------------------- | ------- |
| Z | opcode only | 1 byte | | Z | opcode only | 1 byte |
| B | opcode + a(8) | 2 bytes | | B | opcode + a(8) | 2 bytes |
| BB | opcode + a(8) + b(8) | 3 bytes | | BB | opcode + a(8) + b(8) | 3 bytes |
| BBB | opcode + a(8) + b(8) + c(8) | 4 bytes | | BBB | opcode + a(8) + b(8) + c(8) | 4 bytes |
| BS | opcode + a(8) + b(16) | 4 bytes | | BS | opcode + a(8) + b(16) | 4 bytes |
| BSS | opcode + a(8) + b(16) + c(16) | 6 bytes | | BSS | opcode + a(8) + b(16) + c(16) | 6 bytes |
| S | opcode + a(16) | 3 bytes | | S | opcode + a(16) | 3 bytes |
| W | opcode + a(24) | 4 bytes | | W | opcode + a(24) | 4 bytes |
See [opcode.md](opcode.md) for the full instruction table. See [opcode.md](opcode.md) for the full instruction table.
## OP\_ENTER: Argument Specification ## OP_ENTER: Argument Specification
`OP_ENTER` encodes a method's argument layout in a 24-bit value `OP_ENTER` encodes a method's argument layout in a 24-bit value
(W format). The bit fields are defined by the `MRB_ARGS_*` macros: (W format). The bit fields are defined by the `MRB_ARGS_*` macros:
@@ -258,16 +258,16 @@ string interning for common symbols.
Generated by `lib/mruby/presym.rb`, the presym table maps symbol Generated by `lib/mruby/presym.rb`, the presym table maps symbol
names to compile-time constants: names to compile-time constants:
| Macro | Example | Symbol | | Macro | Example | Symbol |
| ----- | ------- | ------ | | ----------------- | --------------------- | ------------- |
| `MRB_SYM(name)` | `MRB_SYM(initialize)` | `:initialize` | | `MRB_SYM(name)` | `MRB_SYM(initialize)` | `:initialize` |
| `MRB_SYM_B(name)` | `MRB_SYM_B(map)` | `:map!` | | `MRB_SYM_B(name)` | `MRB_SYM_B(map)` | `:map!` |
| `MRB_SYM_Q(name)` | `MRB_SYM_Q(nil)` | `:nil?` | | `MRB_SYM_Q(name)` | `MRB_SYM_Q(nil)` | `:nil?` |
| `MRB_SYM_E(name)` | `MRB_SYM_E(name)` | `:name=` | | `MRB_SYM_E(name)` | `MRB_SYM_E(name)` | `:name=` |
| `MRB_OPSYM(op)` | `MRB_OPSYM(add)` | `:+` | | `MRB_OPSYM(op)` | `MRB_OPSYM(add)` | `:+` |
| `MRB_IVSYM(name)` | `MRB_IVSYM(name)` | `:@name` | | `MRB_IVSYM(name)` | `MRB_IVSYM(name)` | `:@name` |
| `MRB_CVSYM(name)` | `MRB_CVSYM(count)` | `:@@count` | | `MRB_CVSYM(name)` | `MRB_CVSYM(count)` | `:@@count` |
| `MRB_GVSYM(name)` | `MRB_GVSYM(stdout)` | `:$stdout` | | `MRB_GVSYM(name)` | `MRB_GVSYM(stdout)` | `:$stdout` |
## Binary Format (.mrb) ## Binary Format (.mrb)
@@ -299,25 +299,25 @@ mrbc -Boutput source.rb # C array format
## Compilation Limits ## Compilation Limits
| Limit | Value | | Limit | Value |
| ----- | ----- | | ---------------------- | ----------------------------- |
| Max nesting depth | 256 (`MRB_CODEGEN_LEVEL_MAX`) | | Max nesting depth | 256 (`MRB_CODEGEN_LEVEL_MAX`) |
| Max local variables | 255 (uint16 `nlocals`) | | Max local variables | 255 (uint16 `nlocals`) |
| Max symbols per irep | 65535 | | Max symbols per irep | 65535 |
| Max operand (standard) | 255 (8-bit) | | Max operand (standard) | 255 (8-bit) |
| Max operand (extended) | 65535 (16-bit) | | Max operand (extended) | 65535 (16-bit) |
## Source Files ## Source Files
| File | Contents | | File | Contents |
| ---- | -------- | | --------------------------------------- | ------------------------- |
| `mrbgems/mruby-compiler/core/parse.y` | Lrama/Bison grammar | | `mrbgems/mruby-compiler/core/parse.y` | Lrama/Bison grammar |
| `mrbgems/mruby-compiler/core/y.tab.c` | Generated parser | | `mrbgems/mruby-compiler/core/y.tab.c` | Generated parser |
| `mrbgems/mruby-compiler/core/codegen.c` | Code generator | | `mrbgems/mruby-compiler/core/codegen.c` | Code generator |
| `mrbgems/mruby-compiler/core/node.h` | AST node types | | `mrbgems/mruby-compiler/core/node.h` | AST node types |
| `include/mruby/irep.h` | IRep structure definition | | `include/mruby/irep.h` | IRep structure definition |
| `include/mruby/compile.h` | Compiler context API | | `include/mruby/compile.h` | Compiler context API |
| `include/mruby/ops.h` | Opcode definitions | | `include/mruby/ops.h` | Opcode definitions |
| `src/load.c` | Binary format loader | | `src/load.c` | Binary format loader |
| `src/dump.c` | Binary format writer | | `src/dump.c` | Binary format writer |
| `lib/mruby/presym.rb` | Presym table generator | | `lib/mruby/presym.rb` | Presym table generator |
+165 -35
View File
@@ -26,12 +26,12 @@ pauses.
Every heap-allocated object has a color stored in Every heap-allocated object has a color stored in
`RBasic::gc_color` (3 bits): `RBasic::gc_color` (3 bits):
| Color | Value | Meaning | | Color | Value | Meaning |
| ----- | ----- | ------- | | -------------- | ------ | ------------------------------------ |
| White (A or B) | 1 or 2 | Unmarked, candidate for collection | | White (A or B) | 1 or 2 | Unmarked, candidate for collection |
| Gray | 0 | Marked, but children not yet scanned | | Gray | 0 | Marked, but children not yet scanned |
| Black | 4 | Fully marked and scanned | | Black | 4 | Fully marked and scanned |
| Red | 7 | Static/ROM object, never collected | | Red | 7 | Static/ROM object, never collected |
The GC uses two white types (A and B) in a flip-flop scheme. At the The GC uses two white types (A and B) in a flip-flop scheme. At the
start of each GC cycle, the meaning of "current white" is flipped by start of each GC cycle, the meaning of "current white" is flipped by
@@ -107,7 +107,7 @@ The GC operates as a three-state machine:
GC_STATE_ROOT --> GC_STATE_MARK --> GC_STATE_SWEEP --> GC_STATE_ROOT GC_STATE_ROOT --> GC_STATE_MARK --> GC_STATE_SWEEP --> GC_STATE_ROOT
``` ```
### Root Scan (GC\_STATE\_ROOT) ### Root Scan (GC_STATE_ROOT)
Marks objects directly reachable from the VM: Marks objects directly reachable from the VM:
@@ -121,7 +121,7 @@ Marks objects directly reachable from the VM:
After root scanning, the white color is flipped. After root scanning, the white color is flipped.
### Incremental Marking (GC\_STATE\_MARK) ### Incremental Marking (GC_STATE_MARK)
Gray objects are popped from the gray stack and their children Gray objects are popped from the gray stack and their children
marked. Each step processes a limited number of objects: marked. Each step processes a limited number of objects:
@@ -131,13 +131,15 @@ limit = (GC_STEP_SIZE / 100) * step_ratio
``` ```
With default `step_ratio = 200` and `GC_STEP_SIZE = 1024`, the With default `step_ratio = 200` and `GC_STEP_SIZE = 1024`, the
limit is 2048 objects per step. limit is 2048 objects per step. After each step, `gc_debt` is
decremented by the actual number of objects processed, so larger
steps repay more debt.
When the gray stack is exhausted, the final marking phase re-marks When the gray stack is exhausted, the final marking phase re-marks
the arena and global variables to catch objects created during the arena and global variables to catch objects created during
marking, then transitions to sweep. marking, then transitions to sweep.
### Sweep (GC\_STATE\_SWEEP) ### Sweep (GC_STATE_SWEEP)
Iterates through heap pages. For each object: Iterates through heap pages. For each object:
@@ -273,7 +275,7 @@ From Ruby: `GC.generational_mode = true/false`.
`mrb_obj_alloc()` is the core allocation function: `mrb_obj_alloc()` is the core allocation function:
1. If `MRB_GC_STRESS` is defined, run a full GC 1. If `MRB_GC_STRESS` is defined, run a full GC
2. If `gc->live >= gc->threshold`, run `mrb_incremental_gc()` 2. Increment `gc->gc_debt`; if positive, run `mrb_incremental_gc()`
3. Ensure arena has space (`gc_arena_keep`) 3. Ensure arena has space (`gc_arena_keep`)
4. Pop an object from the freelist of `gc->free_heaps` 4. Pop an object from the freelist of `gc->free_heaps`
5. If no free pages, allocate a new page (`add_heap`) 5. If no free pages, allocate a new page (`add_heap`)
@@ -299,18 +301,43 @@ The object's type is set to `MRB_TT_FREE` after freeing.
## Triggering GC ## Triggering GC
### Automatic ### Debt Model
GC runs automatically when `gc->live >= gc->threshold` during GC uses a **debt-based feedback model** to balance allocation
object allocation. After each cycle: rate against collection work. The key field is `gc->gc_debt`
(signed integer):
- **Negative** = credit (GC is ahead, no collection needed)
- **Zero** = balanced
- **Positive** = debt (allocation outpacing collection, GC runs)
Each object allocation increments `gc_debt` by 1. When debt
goes positive, `mrb_incremental_gc()` runs. Each incremental
step decrements debt by `GC_STEP_SIZE` (1024), giving credit
for many future allocations.
When a GC cycle completes, credit is calculated from
`interval_ratio`:
```text ```text
threshold = (live_after_mark / 100) * interval_ratio credit = (live_after_mark / 100) * interval_ratio - live_after_mark
minimum: GC_STEP_SIZE (1024) minimum: GC_STEP_SIZE (1024)
gc_debt = -credit
``` ```
With default `interval_ratio = 200`, GC triggers when live objects With default `interval_ratio = 200` and 1000 live objects:
roughly double. `credit = (1000/100)*200 - 1000 = 1000`, so approximately 1000
allocations can occur before the next GC cycle begins.
### Malloc Pressure
When `gc->malloc_threshold` is set (non-zero), the GC also
tracks bytes allocated through `mrb_realloc_simple()` in
`gc->malloc_increase`. When `malloc_increase` exceeds
`malloc_threshold`, the counter resets and an incremental GC
step runs. This captures memory pressure from large buffers
(e.g., long strings) that would otherwise be invisible to the
object-count-based debt model.
### Manual ### Manual
@@ -325,33 +352,136 @@ From Ruby: `GC.start`.
### Compile-Time ### Compile-Time
| Macro | Default | Description | | Macro | Default | Description |
| ----- | ------- | ----------- | | ------------------------------ | ------- | --------------------------------------- |
| `MRB_HEAP_PAGE_SIZE` | 1024 | Objects per heap page | | `MRB_HEAP_PAGE_SIZE` | 1024 | Objects per heap page |
| `MRB_GRAY_STACK_SIZE` | 1024 | Gray stack capacity | | `MRB_GRAY_STACK_SIZE` | 1024 | Gray stack capacity |
| `MRB_GC_ARENA_SIZE` | 100 | Arena size (fixed mode) or initial size | | `MRB_GC_ARENA_SIZE` | 100 | Arena size (fixed mode) or initial size |
| `MRB_GC_FIXED_ARENA` | off | Use fixed-size arena | | `MRB_GC_FIXED_ARENA` | off | Use fixed-size arena |
| `MRB_GC_TURN_OFF_GENERATIONAL` | off | Disable generational mode | | `MRB_GC_TURN_OFF_GENERATIONAL` | off | Disable generational mode |
| `MRB_GC_STRESS` | off | Full GC on every allocation (debug) | | `MRB_GC_STRESS` | off | Full GC on every allocation (debug) |
| `MRB_USE_MALLOC_TRIM` | off | Call `malloc_trim()` after full GC | | `MRB_GC_STATS` | off | Enable GC statistics counters |
| `MRB_USE_MALLOC_TRIM` | off | Call `malloc_trim()` after full GC |
### Runtime ### Runtime
From Ruby code: From Ruby code:
```ruby ```ruby
GC.interval_ratio = 200 # threshold = live * ratio / 100 GC.interval_ratio = 200 # controls debt credit after GC cycle
GC.step_ratio = 200 # objects per incremental step GC.step_ratio = 200 # objects per incremental step
GC.step_limit = 0 # 0=unlimited, >0=absolute step cap
GC.malloc_threshold = 0 # 0=disabled, >0=bytes to trigger GC
GC.generational_mode = true GC.generational_mode = true
GC.start # force full GC GC.start # force full GC
GC.enable # re-enable GC GC.enable # re-enable GC
GC.disable # disable GC GC.disable # disable GC
``` ```
### GC Statistics
`GC.stat` returns a Hash with GC state and statistics:
```ruby
GC.stat
# => {
# :live => 5432, # live object count
# :debt => -1024, # GC debt (negative=credit, positive=behind)
# :state => 0, # 0=root, 1=marking, 2=sweeping
# :generational => true, # generational mode enabled
# :full => false, # major GC in progress
# :step_limit => 0, # current step limit setting
# :malloc_increase => 8192, # malloc bytes since last cycle
# :malloc_threshold => 0, # current malloc threshold setting
# }
```
With `MRB_GC_STATS` enabled, additional keys are available:
```ruby
# :total => 15, # total GC invocations
# :minor => 12, # minor GC count
# :major => 3, # major GC count
```
### Tuning Guide
**`interval_ratio`** (default 200): Controls how many allocations
occur between GC cycles. Higher values reduce GC frequency but
increase peak memory. The debt credit after each cycle is
`(live_after_mark / 100) * interval_ratio - live_after_mark`.
**`step_ratio`** (default 200): Controls how much work each
incremental step performs. Higher values make each step larger,
reducing total GC overhead but increasing individual pause times.
**`step_limit`** (default 0, unlimited): Caps the maximum work
per incremental step regardless of `step_ratio`. Useful for
real-time applications that need bounded pause times. The
effective step size is `min(step_ratio calculation, step_limit)`.
**`malloc_threshold`** (default 0, disabled): Triggers GC when
cumulative `malloc`/`realloc` bytes exceed this threshold. Useful
when applications allocate large buffers (strings, data objects)
that create memory pressure without proportional object count
increase.
### Practical Tuning Examples
**Allocation-heavy workloads** (many short-lived Procs, closures,
blocks): GC sweep dominates because of high object churn. Increase
`interval_ratio` to reduce GC frequency:
```ruby
GC.interval_ratio = 400 # ~12% faster than default (200)
```
Higher values (400-600) reduce sweep overhead at the cost of more
dead objects accumulating before collection. Values above 600 show
diminishing returns. Peak memory usage increases temporarily, but
live object count after GC remains the same.
**CPU-intensive workloads** (numeric computation, recursive methods
with no object allocation): GC parameters have negligible impact
because GC rarely runs. No tuning needed.
**Real-time or latency-sensitive** applications: Use `step_limit`
to bound pause times:
```ruby
GC.step_limit = 256 # cap incremental step to 256 objects
```
This makes GC pauses more predictable but increases total GC
overhead (more steps needed per cycle).
**Large buffer workloads** (reading files, building long strings):
Set `malloc_threshold` to trigger GC when buffer allocations
accumulate, even if object count is low:
```ruby
GC.malloc_threshold = 1024 * 1024 # trigger GC per ~1MB allocated
```
### Diagnosing GC Overhead
Use `GC.stat` to monitor GC behavior at runtime:
```ruby
s = GC.stat
puts "live objects: #{s[:live]}"
puts "GC debt: #{s[:debt]}" # positive = GC is behind
puts "GC state: #{s[:state]}" # 0=idle, 1=marking, 2=sweeping
```
If `debt` is frequently positive during performance-critical
sections, increase `interval_ratio`. If memory usage is too high,
decrease it.
## Source Files ## Source Files
| File | Contents | | File | Contents |
| ---- | -------- | | -------------------- | --------------------------------- |
| `src/gc.c` | GC implementation (~1400 lines) | | `src/gc.c` | GC implementation |
| `include/mruby/gc.h` | `mrb_gc` structure, public GC API | | `include/mruby/gc.h` | `mrb_gc` structure, public GC API |
| `include/mruby.h` | Arena save/restore macros | | `include/mruby.h` | Arena save/restore macros |
+19 -19
View File
@@ -97,12 +97,12 @@ return n + 1 (skip self)
### Call Context Info (cci) ### Call Context Info (cci)
| Value | Name | Meaning | | Value | Name | Meaning |
| ----- | ---- | ------- | | ----- | --------------- | ------------------------------------- |
| 0 | `CINFO_NONE` | Normal VM-to-VM call | | 0 | `CINFO_NONE` | Normal VM-to-VM call |
| 1 | `CINFO_DIRECT` | Explicit VM call (block, lambda.call) | | 1 | `CINFO_DIRECT` | Explicit VM call (block, lambda.call) |
| 2 | `CINFO_SKIP` | Skip frame in stack traces | | 2 | `CINFO_SKIP` | Skip frame in stack traces |
| 3 | `CINFO_RESUMED` | Fiber resumed (stop execution) | | 3 | `CINFO_RESUMED` | Fiber resumed (stop execution) |
## Dispatch Loop ## Dispatch Loop
@@ -241,13 +241,13 @@ correctness.
### Proc Types ### Proc Types
| Flag | Meaning | | Flag | Meaning |
| ---- | ------- | | ------------------- | ------------------------------ |
| `MRB_PROC_CFUNC_FL` | C function (not irep-based) | | `MRB_PROC_CFUNC_FL` | C function (not irep-based) |
| `MRB_PROC_STRICT` | Lambda (strict argument check) | | `MRB_PROC_STRICT` | Lambda (strict argument check) |
| `MRB_PROC_ORPHAN` | No environment attachment | | `MRB_PROC_ORPHAN` | No environment attachment |
| `MRB_PROC_ENVSET` | Has captured environment | | `MRB_PROC_ENVSET` | Has captured environment |
| `MRB_PROC_SCOPE` | Defines a new variable scope | | `MRB_PROC_SCOPE` | Defines a new variable scope |
## Fiber Switching ## Fiber Switching
@@ -317,9 +317,9 @@ ensuring the incremental GC correctly tracks live references.
## Source Files ## Source Files
| File | Contents | | File | Contents |
| ---- | -------- | | ----------------------- | ---------------------------------------------- |
| `src/vm.c` | Dispatch loop, method invocation (~1900 lines) | | `src/vm.c` | Dispatch loop, method invocation (~1900 lines) |
| `include/mruby.h` | `mrb_state`, `mrb_callinfo`, `mrb_context` | | `include/mruby.h` | `mrb_state`, `mrb_callinfo`, `mrb_context` |
| `include/mruby/proc.h` | `RProc`, `REnv` structures | | `include/mruby/proc.h` | `RProc`, `REnv` structures |
| `include/mruby/throw.h` | `MRB_TRY`/`MRB_CATCH` macros | | `include/mruby/throw.h` | `MRB_TRY`/`MRB_CATCH` macros |
+168 -9
View File
@@ -15,6 +15,23 @@ This document is collecting these limitations.
This document does not contain a complete list of limitations. This document does not contain a complete list of limitations.
Please help to improve it by submitting your findings. Please help to improve it by submitting your findings.
## Features provided by mrbgems
Many Ruby features that CRuby builds into its core are provided by
mrbgems in mruby. Which features are actually available depends on
which mrbgems are linked into the build. The `default.gembox` and
`stdlib.gembox` cover the common cases, but a minimal build can omit
familiar features such as `Kernel#binding` (provided by
`mruby-binding`), `Kernel#catch`/`throw` (by `mruby-catch`),
`Enumerable` extensions, `Comparable`, IO, regular expressions, and
many more.
This is by design rather than a limitation per se. When porting Ruby
code to mruby, a `NoMethodError` or `NameError` often means "the gem
providing this feature is not linked in" rather than "mruby does not
support it." Adding the relevant gem to the build configuration is
usually enough.
## `Kernel.raise` in rescue clause ## `Kernel.raise` in rescue clause
`Kernel.raise` without arguments does not raise the current exception within `Kernel.raise` without arguments does not raise the current exception within
@@ -133,12 +150,6 @@ The re-defined `+` operator does not accept any arguments.
`'ab'` `'ab'`
Behavior of the operator wasn't changed. Behavior of the operator wasn't changed.
## `Kernel#binding` is not supported without mruby-binding gem
`Kernel#binding` method requires the `mruby-binding` gem (included
in the `metaprog` gembox). Without this gem, `binding` is not
available.
## `nil?` redefinition in conditional expressions ## `nil?` redefinition in conditional expressions
Redefinition of `nil?` is ignored in conditional expressions. Redefinition of `nil?` is ignored in conditional expressions.
@@ -274,11 +285,11 @@ enabled with the `MRB_UTF8_STRING` compile flag.
Integer size depends on the value boxing configuration: Integer size depends on the value boxing configuration:
| Configuration | Integer range | | Configuration | Integer range |
| ------------- | ------------- | | ----------------------------- | ---------------- |
| Word boxing, 64-bit (default) | roughly +/- 2^62 | | Word boxing, 64-bit (default) | roughly +/- 2^62 |
| Word boxing, 32-bit (default) | roughly +/- 2^30 | | Word boxing, 32-bit (default) | roughly +/- 2^30 |
| NaN boxing (64-bit only) | -2^31 to 2^31-1 | | NaN boxing (64-bit only) | -2^31 to 2^31-1 |
Code relying on 64-bit integer precision may behave differently Code relying on 64-bit integer precision may behave differently
across configurations. The `mruby-bigint` gem provides across configurations. The `mruby-bigint` gem provides
@@ -290,3 +301,151 @@ arbitrary-precision integers when included.
(included in the `stdlib` gembox). Even with the gem, (included in the `stdlib` gembox). Even with the gem,
`ObjectSpace.each_object` has limited functionality compared `ObjectSpace.each_object` has limited functionality compared
to CRuby. to CRuby.
## No Implicit Type Conversion (`to_int`, `to_str`, `to_ary`, ...)
mruby does not perform implicit type conversion through methods
like `to_int`, `to_str`, `to_ary`, or `to_hash`. CRuby uses these
to let user-defined classes duck-type as built-in types — for
example `Array#[]` calls `to_int` on its argument, `String#+` calls
`to_str`, and multiple assignment calls `to_ary` on its right-hand
side. mruby's built-in operations require the actual built-in type
and do not consult these conversion methods.
```ruby
class MyInt; def to_int; 42; end; end
class MyStr; def to_str; "x"; end; end
class MyAry; def to_ary; [1,2,3]; end; end
```
#### CRuby
```
[1,2,3][MyInt.new] # => nil (to_int called -> ary[42])
"a" + MyStr.new # => "ax" (to_str called)
a, b, c = MyAry.new # => a=1, b=2, c=3 (to_ary called)
```
#### mruby
```
[1,2,3][MyInt.new] # TypeError
"a" + MyStr.new # TypeError
a, b, c = MyAry.new # a=<MyAry obj>, b=nil, c=nil (treated as single value)
```
Identity versions of `to_int`, `to_str`, `to_sym`, and `to_hash`
remain defined on the corresponding built-in types so that
`respond_to?(:to_str)`-style checks work for built-in instances.
`Float#to_int` and `Array#to_ary` are intentionally not defined.
Explicit conversion methods (`to_i`, `to_s`, `to_a`) work as in
CRuby and are called by features such as string interpolation and
the splat operator (`*obj`).
This is a deliberate trade-off: implicit conversion forces every
coercion site to go through method dispatch and can silently mask
type-mismatch bugs.
## Nested `def` in Singleton-Method Context
`def` written inside a singleton method (`def self.foo`) is placed
on a different class in mruby than in CRuby. CRuby registers the
inner method as an instance method of the lexical enclosing class.
mruby registers it as a method of the enclosing receiver's
singleton class, which makes it visible as a class method of the
enclosing class.
```ruby
class SomeClass
def self.class_method
def nested; 'nested!'; end
end
end
SomeClass.class_method
```
#### CRuby
```
SomeClass.nested # NoMethodError
SomeClass.new.nested # => "nested!" (instance method)
```
#### mruby
```
SomeClass.nested # => "nested!" (class method)
SomeClass.new.nested # NoMethodError
```
Writing nested `def` like this is unusual; this difference rarely
surfaces in practical code.
## `Proc#dup` / `Proc#clone` is Always Orphan
A `dup` or `clone` of a block given to a method is always treated as
an orphan block in mruby — calling it raises `LocalJumpError` if the
block contains `break` or `return`. CRuby is finer-grained: the copy
inherits the orphan status of its original, so the copy only becomes
orphan once the original yielding method returns.
```ruby
def m(&b)
b.dup
end
x = m { break 1 }
x.call
```
#### CRuby
```
LocalJumpError # raised only after m returns; if called inside m,
# the dup is still a live block
```
#### mruby
```
LocalJumpError # always raised — the dup is orphan from the moment
# it is created
```
mruby's stricter rule keeps `RProc` from needing a back-pointer to
the original block (which would also enlarge the GC mark set).
## `Class#initialize` Can Be Re-Invoked
CRuby raises `TypeError: already initialized class` when `initialize`
is invoked on a class that has already been set up. mruby's
`Class#initialize` has no such guard — invoking it on an existing
class through `__send__`, `send`, or `UnboundMethod#bind_call`
silently succeeds. The superclass argument is ignored in this case,
so the call cannot rewrite the class hierarchy; only the block (if
any) is evaluated with the class as receiver.
```ruby
Klass = Class.new
Klass.__send__(:initialize) {}
```
#### CRuby
```
TypeError: already initialized class
```
#### mruby
```
The block is evaluated in the context of Klass; no error is raised.
The superclass is not changed even when one is passed as an argument.
```
`Module#initialize` is re-callable in both implementations, so this
divergence is `Class`-specific. Adding the CRuby check would require
an additional flag bit on every `RClass`; mruby leaves the bit
unspent because no destructive side effects are possible through
this path.
+367
View File
@@ -0,0 +1,367 @@
# User visible changes in `mruby4.0` from `mruby3.4`
"**_NOTE_**:" are changes to be aware of.
# The language
## Pattern Matching
mruby now supports pattern matching (case/in) syntax:
- Basic pattern matching with `case`/`in` syntax ([dadfac6](https://github.com/mruby/mruby/commit/dadfac6))
- Array pattern matching ([ec67fd9](https://github.com/mruby/mruby/commit/ec67fd9))
- Hash pattern matching ([2147263](https://github.com/mruby/mruby/commit/2147263))
- Find pattern matching (`[*pre, target, *post]`) ([6c4d98b](https://github.com/mruby/mruby/commit/6c4d98b))
- Pin operator (`^variable`) ([1de6340](https://github.com/mruby/mruby/commit/1de6340))
- Guard clauses (`if`/`unless` conditions) ([07ac110](https://github.com/mruby/mruby/commit/07ac110))
- One-line pattern matching (`expr in pattern`) ([e76ce24](https://github.com/mruby/mruby/commit/e76ce24))
- Brace-less hash pattern support ([e8096bf](https://github.com/mruby/mruby/commit/e8096bf))
## Other Language Changes
- `&nil` in formal parameters to explicitly opt out of block arguments ([b07518e](https://github.com/mruby/mruby/commit/b07518e))
- Trailing comma in method definition parameters: `def foo(a, b,)` ([f78334b](https://github.com/mruby/mruby/commit/f78334b))
- Array/Hash/String subclasses can now override `[]` and `[]=` methods ([#6675](https://github.com/mruby/mruby/pull/6675))
- `OP_SETIDX` optimization for Array and Hash ([ddd8fe1](https://github.com/mruby/mruby/commit/ddd8fe1))
- `case`/`in` without `else` now raises `NoMatchingPatternError` ([d8de35b](https://github.com/mruby/mruby/commit/d8de35b))
- Allow compound statement in parenthesized argument context ([919cbd8](https://github.com/mruby/mruby/commit/919cbd8))
# Changes in C API
- **_NOTE_**: `mrb_alloca()` renamed to `mrb_temp_alloc()` ([7fe5c2e](https://github.com/mruby/mruby/commit/7fe5c2e))
- **_NOTE_**: `mruby/ext/io.h` renamed to `mruby/io.h` ([2813f79](https://github.com/mruby/mruby/commit/2813f79))
- `mrb_gc_add_region()` for contiguous heap region support ([072855a](https://github.com/mruby/mruby/commit/072855a))
- `mrb_class_outer()` to get the outer class/module ([3a1b771](https://github.com/mruby/mruby/commit/3a1b771))
- `MRB_ENSURE()` macro for exception-safe cleanup ([3ac682b](https://github.com/mruby/mruby/commit/3ac682b))
- `mrb_time_get_tm()` for accessing struct tm ([daaaafe](https://github.com/mruby/mruby/commit/daaaafe))
- `MRB_OPEN_FAILURE()` macro for checking mrb_open result ([40b0cb9](https://github.com/mruby/mruby/commit/40b0cb9))
- `mrb_print_error()` now handles NULL gracefully ([8e50a45](https://github.com/mruby/mruby/commit/8e50a45))
- `mrb_open()` returns mrb_state with exc set on init failure ([05ffe0c](https://github.com/mruby/mruby/commit/05ffe0c))
- `mrb_utf8_to_buf()` for UTF-8 encoding consolidation ([7e28e68](https://github.com/mruby/mruby/commit/7e28e68))
- `kh_is_end()` macro for safe khash iteration ([893cc75](https://github.com/mruby/mruby/commit/893cc75))
- `mrb_bigint_p()` always defined regardless of bigint gem presence ([6c4a8c0](https://github.com/mruby/mruby/commit/6c4a8c0))
- `RInteger` and `RFloat` added to `RVALUE` union ([13dbca0](https://github.com/mruby/mruby/commit/13dbca0))
# ROM Method Tables
All built-in classes and most extension gems now use read-only method
tables stored in `.rodata` instead of heap-allocated hash tables. Method
definitions no longer consume heap memory, significantly reducing memory
footprint for embedded use.
Core classes converted: BasicObject, Object, Module, Class, Kernel,
String, Array, Hash, Numeric, Integer, Float, NilClass, TrueClass,
FalseClass, Range, Symbol, Exception, Proc.
Extension gems converted: mruby-string-ext, mruby-array-ext, mruby-set,
mruby-struct, mruby-class-ext, mruby-numeric-ext, mruby-random,
mruby-kernel-ext, mruby-complex, mruby-rational, mruby-io, mruby-socket,
mruby-method, mruby-metaprog, mruby-time, mruby-hash-ext, mruby-proc-ext,
mruby-symbol-ext, mruby-range-ext, mruby-object-ext.
# GC and Memory
- **_NOTE_**: `MRB_NO_PRESYM` removed; presym is now always enabled ([81689045](https://github.com/mruby/mruby/commit/81689045))
- Replace `gcnext` gray linked list with fixed-size gray stack, reducing per-object overhead ([31fea170](https://github.com/mruby/mruby/commit/31fea170))
- `mrb_gc_add_region()` for providing contiguous memory buffers as GC heap pages ([072855a](https://github.com/mruby/mruby/commit/072855a))
- Chunk-based pool for symbol string allocation ([e05bd8f](https://github.com/mruby/mruby/commit/e05bd8f))
- Reduce `IV_INITIAL_SIZE` from 4 to 2 ([6bd1f51](https://github.com/mruby/mruby/commit/6bd1f51))
- Lossless float encoding using rotation in word boxing ([b6148c8](https://github.com/mruby/mruby/commit/b6148c8))
- Lossless rotation encoding for 32-bit float32 word boxing ([14a5cfb](https://github.com/mruby/mruby/commit/14a5cfb))
- Consolidated irep allocation for .mrb loading ([74fb045](https://github.com/mruby/mruby/commit/74fb045))
- Object shapes (hidden classes) for `MRB_TT_OBJECT` IV storage, sharing key layouts across objects with the same instance variable assignment order ([8d10056](https://github.com/mruby/mruby/commit/8d10056))
# Build & Configuration
- **_NOTE_**: `MRB_WORDBOX_NO_FLOAT_TRUNCATE` renamed to `MRB_WORDBOX_NO_INLINE_FLOAT` (old name still works) ([59e1fe2](https://github.com/mruby/mruby/commit/59e1fe2))
- **_NOTE_**: `MRB_INT64` on 32-bit now requires `MRB_NO_BOXING` (other boxing modes cannot guarantee alignment for heap-allocated 64-bit integers) ([eaaa66b](https://github.com/mruby/mruby/commit/eaaa66b))
- Amalgamation support via `rake amalgam` task ([d995ca2](https://github.com/mruby/mruby/commit/d995ca2))
- New Platform: Cosmopolitan Libc ([#6681](https://github.com/mruby/mruby/pull/6681))
- Emscripten: use native WASM exception handling ([ca364e3](https://github.com/mruby/mruby/commit/ca364e3))
- HAL (Hardware Abstraction Layer) for platform abstraction in mruby-io, mruby-socket, mruby-dir, mruby-task ([74ca22f](https://github.com/mruby/mruby/commit/74ca22f))
- `MRUBY_MIRB_READLINE` environment variable to control readline library selection ([0aafb83](https://github.com/mruby/mruby/commit/0aafb83))
- MSYS2 drive letter support in build script ([77f6ffe](https://github.com/mruby/mruby/commit/77f6ffe))
- Inter-gem headers separated from external API headers ([#6671](https://github.com/mruby/mruby/pull/6671))
# Changes in mrbgems
## New Gems
- **mruby-task**: Cooperative multitasking with preemptive scheduling ([ae0d7a0](https://github.com/mruby/mruby/commit/ae0d7a0))
- **mruby-benchmark**: Benchmarking gem ([2f40f3d](https://github.com/mruby/mruby/commit/2f40f3d))
- **mruby-strftime**: Time#strftime implementation ([b31e22f](https://github.com/mruby/mruby/commit/b31e22f))
## mruby-bin-mirb Improvements
- Custom multi-line editor replacing readline ([527018c](https://github.com/mruby/mruby/commit/527018c))
- Syntax highlighting for keywords, strings, result values, hash key symbols ([624272b](https://github.com/mruby/mruby/commit/624272b), [1713d4a](https://github.com/mruby/mruby/commit/1713d4a))
- Automatic light/dark theme detection via OSC 11 ([db4c8d9](https://github.com/mruby/mruby/commit/db4c8d9))
- Tab completion support ([2f15282](https://github.com/mruby/mruby/commit/2f15282))
- Colored output for prompts and errors ([b36e0b4](https://github.com/mruby/mruby/commit/b36e0b4))
- Auto-indentation and auto-dedent ([d52f318](https://github.com/mruby/mruby/commit/d52f318), [e901b6d](https://github.com/mruby/mruby/commit/e901b6d))
- Command history with Up/Down navigation ([5f85c1b](https://github.com/mruby/mruby/commit/5f85c1b))
- Line numbers in multi-line prompts ([5a3f0e2](https://github.com/mruby/mruby/commit/5a3f0e2))
- UTF-8 multibyte character support ([4a97da3](https://github.com/mruby/mruby/commit/4a97da3))
## mruby-bigint Improvements
- Toom-3 multiplication for large numbers ([99620804](https://github.com/mruby/mruby/commit/99620804))
- Karatsuba multiplication for medium-sized numbers ([85e81072](https://github.com/mruby/mruby/commit/85e81072))
- Balance multiplication for asymmetric operands ([0220ec2b](https://github.com/mruby/mruby/commit/0220ec2b))
- Divide-and-conquer optimization for `to_s` ([990ff90f](https://github.com/mruby/mruby/commit/990ff90f))
- Consolidated mpn layer for low-level limb operations ([9ef3362f](https://github.com/mruby/mruby/commit/9ef3362f))
- Always use 32-bit limbs by default ([c747c77f](https://github.com/mruby/mruby/commit/c747c77f))
## Other Gem Changes
- **_NOTE_**: `Hash#deconstruct_keys` removed for CRuby compatibility ([34b9412](https://github.com/mruby/mruby/commit/34b9412))
- **mruby-enum-lazy**: Fix `Lazy#flat_map` to handle non-enumerable block return values ([#6765](https://github.com/mruby/mruby/pull/6765))
- **mruby-array-ext**: Add `Array#find` and `Array#rfind` methods
- **mruby-io**: Add `IO#putc` and `Kernel#putc` ([baff6e6](https://github.com/mruby/mruby/commit/baff6e6))
- **mruby-random**: Replace xoshiro with PCG for better memory efficiency ([f1bab01](https://github.com/mruby/mruby/commit/f1bab01))
- **mruby-compiler**: Variable-sized AST nodes for reduced memory usage
- **mruby-compiler**: `no_return_value` context flag for script optimization ([613b03a](https://github.com/mruby/mruby/commit/613b03a))
- `initialize_copy` and `respond_to_missing?` defined as private ([#6708](https://github.com/mruby/mruby/pull/6708))
- Struct keyword argument initialization ([#6574](https://github.com/mruby/mruby/pull/6574))
# Compiler Improvements
- Variable-sized AST nodes for reduced memory consumption ([821b989](https://github.com/mruby/mruby/commit/821b989))
- Pattern matching bytecode optimizations ([21d4135](https://github.com/mruby/mruby/commit/21d4135))
- Optimized masgn to generate literals directly into target registers ([fb5d966](https://github.com/mruby/mruby/commit/fb5d966))
- Optimized splat of literal arrays in args/literals ([1cb8d73](https://github.com/mruby/mruby/commit/1cb8d73))
- Early termination after too many parse errors ([510ebd7](https://github.com/mruby/mruby/commit/510ebd7))
- Chunk array literals at 64 elements to reduce register pressure ([f98d641](https://github.com/mruby/mruby/commit/f98d641))
- Chunk `%w()` and `%i()` literals to reduce register pressure ([62cf0dc](https://github.com/mruby/mruby/commit/62cf0dc))
# VM Optimizations
New super-instructions that fuse common opcode sequences to reduce bytecode size and improve performance:
- `OP_SEND0`/`OP_SSEND0`: Zero-argument method call, avoiding argument count setup ([9123ef4](https://github.com/mruby/mruby/commit/9123ef4))
- `OP_TDEF`/`OP_SDEF`: Fused method definition combining TCLASS/SCLASS+METHOD+DEF into single instruction, saving 4 bytes per method ([8d4f47e](https://github.com/mruby/mruby/commit/8d4f47e))
- `OP_GETIDX0`: Fast path for `array[0]` and `Array#first` access ([680f7ec](https://github.com/mruby/mruby/commit/680f7ec))
- `OP_ADDILV`/`OP_SUBILV`: Local variable increment/decrement fusion for `i += n` patterns ([43f64b9](https://github.com/mruby/mruby/commit/43f64b9))
- `OP_RETSELF`: Single-byte instruction for `return self` pattern ([a71db8c](https://github.com/mruby/mruby/commit/a71db8c))
- `OP_RETNIL`: Single-byte instruction for `return nil` pattern ([64e30bf](https://github.com/mruby/mruby/commit/64e30bf))
- `OP_RETTRUE`/`OP_RETFALSE`: Single-byte instructions for `return true`/`return false` patterns ([0b15727](https://github.com/mruby/mruby/commit/0b15727))
- `OP_MATCHERR`: Pattern matching error with conditional execution ([944168a](https://github.com/mruby/mruby/commit/944168a))
- `OP_BLKCALL`: Direct block call for `yield`, bypassing method dispatch (13-17% faster) ([3aa2872](https://github.com/mruby/mruby/commit/3aa2872))
Other optimizations:
- 1.5x stack growth instead of linear growth for reduced reallocations ([f7988c93](https://github.com/mruby/mruby/commit/f7988c93))
- Skip keyword argument hash duplication ([5970e350](https://github.com/mruby/mruby/commit/5970e350))
# Fixed GitHub Issues
- [#5531](https://github.com/mruby/mruby/issues/5531) Hash recursion detection
- [#6506](https://github.com/mruby/mruby/issues/6506) Constant lookup in singleton class
- [#6507](https://github.com/mruby/mruby/issues/6507) tally multi-values
- [#6508](https://github.com/mruby/mruby/issues/6508) Enumerable#sum index
- [#6509](https://github.com/mruby/mruby/issues/6509) scope_new nregs initialization
- [#6515](https://github.com/mruby/mruby/issues/6515) y.tab.c in repository
- [#6516](https://github.com/mruby/mruby/issues/6516) Private backquote
- [#6554](https://github.com/mruby/mruby/issues/6554) Socket private #initialize
- [#6570](https://github.com/mruby/mruby/issues/6570) instance_eval crash
- [#6613](https://github.com/mruby/mruby/issues/6613) const_added hook during bootstrapping
- [#6635](https://github.com/mruby/mruby/issues/6635), [#6636](https://github.com/mruby/mruby/issues/6636) Colon3 constant lookup
- [#6637](https://github.com/mruby/mruby/issues/6637) arm64 mingw64 builtin setjmp/longjmp
- [#6642](https://github.com/mruby/mruby/issues/6642) Task segfault when sleep called from C
- [#6645](https://github.com/mruby/mruby/issues/6645) Set memory leak from double initialization
- [#6646](https://github.com/mruby/mruby/issues/6646) IO#gets negative length
- [#6647](https://github.com/mruby/mruby/issues/6647) IO#ungetc buffer overflow
- [#6648](https://github.com/mruby/mruby/issues/6648) sprintf buffer overread
- [#6649](https://github.com/mruby/mruby/issues/6649) Array#sort! use-after-realloc
- [#6650](https://github.com/mruby/mruby/issues/6650) Array#fill validation
- [#6652](https://github.com/mruby/mruby/issues/6652) Array comparison use-after-realloc
- [#6657](https://github.com/mruby/mruby/issues/6657) Exception handling for ||= on class variables
- [#6659](https://github.com/mruby/mruby/issues/6659) Super with keyword arguments
- [#6660](https://github.com/mruby/mruby/issues/6660) Regression on struct/array/hash == override with super
- [#6662](https://github.com/mruby/mruby/issues/6662) Array set operations use-after-free
- [#6664](https://github.com/mruby/mruby/issues/6664) Set#flatten memory leak
- [#6666](https://github.com/mruby/mruby/issues/6666) Regexp literal with encoding
- [#6668](https://github.com/mruby/mruby/issues/6668) Method#== for aliased methods and comparison bug
- [#6671](https://github.com/mruby/mruby/issues/6671) Separate inter-gem headers from external API headers
- [#6674](https://github.com/mruby/mruby/issues/6674) Document pattern matching limitations
- [#6675](https://github.com/mruby/mruby/issues/6675) Allow Hash#[] to be aliased again
- [#6687](https://github.com/mruby/mruby/issues/6687) Expand MRB_SYM/MRB_GVSYM support for symbols with special characters
- [#6698](https://github.com/mruby/mruby/issues/6698) Bigint tests fail on architectures other than x86_64 and i386
- [#6701](https://github.com/mruby/mruby/issues/6701) Heap-use-after-free in mrb_vm_exec involving mruby-rational / mruby-bigint
- [#6702](https://github.com/mruby/mruby/issues/6702) mruby-bigint doesn't compile in C++ project
- [#6704](https://github.com/mruby/mruby/issues/6704) Heap-buffer-overflow in mrb_vm_exec via malformed source code
- [#6705](https://github.com/mruby/mruby/issues/6705) Can't get outer class of an object in C
- [#6713](https://github.com/mruby/mruby/issues/6713) mruby-polarssl not work
- [#6720](https://github.com/mruby/mruby/issues/6720) Random float range: different behavior from CRuby
- [#6722](https://github.com/mruby/mruby/issues/6722) RBreak size overflow on 32-bit platforms with MRB_NO_BOXING
- [#6740](https://github.com/mruby/mruby/issues/6740) `%w()`/`%i()` register pressure with large literals
- [#6741](https://github.com/mruby/mruby/issues/6741) `case`/`in` without `else` should raise `NoMatchingPatternError`
- [#6760](https://github.com/mruby/mruby/issues/6760) `mrb_gc_unregister()` not removing all matching entries
# Merged Pull Requests
- [#6418](https://github.com/mruby/mruby/pull/6418) Add `ls-lint` with GitHub Actions
- [#6492](https://github.com/mruby/mruby/pull/6492) fix a typo, update specs
- [#6493](https://github.com/mruby/mruby/pull/6493) Fix TYPO in memory.md
- [#6495](https://github.com/mruby/mruby/pull/6495) Remove `MRB_ENDIAN_LOHI()` that is no longer in use
- [#6497](https://github.com/mruby/mruby/pull/6497) gha: update `build.yml` try `windows-2025` image
- [#6498](https://github.com/mruby/mruby/pull/6498) Clean up and standardize the pre-commit config
- [#6501](https://github.com/mruby/mruby/pull/6501) Update pre-commit Node.js version to `v22.14.0 LTS`
- [#6502](https://github.com/mruby/mruby/pull/6502) pre-commit: update prettier to the latest version
- [#6503](https://github.com/mruby/mruby/pull/6503) misc: fix typos
- [#6505](https://github.com/mruby/mruby/pull/6505) mrbgems: fix spelling
- [#6510](https://github.com/mruby/mruby/pull/6510) Fixed class method visibility via `module_function`
- [#6511](https://github.com/mruby/mruby/pull/6511) Exclude the external project "lrama" from pre-commit
- [#6513](https://github.com/mruby/mruby/pull/6513) mruby 3.4.0 released
- [#6517](https://github.com/mruby/mruby/pull/6517) core/codegen.c: remove unneeded duplicate semicolon
- [#6518](https://github.com/mruby/mruby/pull/6518) Change mrbc_args.flags bit width from 2 to 3
- [#6519](https://github.com/mruby/mruby/pull/6519) Add `tools/lrama` to `.prettierignore`
- [#6520](https://github.com/mruby/mruby/pull/6520) pre-commit: autoupdate and update node LTS version
- [#6521](https://github.com/mruby/mruby/pull/6521) Add codespell config file `.codespellrc`
- [#6522](https://github.com/mruby/mruby/pull/6522) gha: label more files
- [#6523](https://github.com/mruby/mruby/pull/6523) add `rand(Range)` and unify implementations of `Random#rand` and `Kernel#rand`
- [#6524](https://github.com/mruby/mruby/pull/6524) Fix Kernel#p when no argument
- [#6525](https://github.com/mruby/mruby/pull/6525) Skip adding empty input to mirb history
- [#6526](https://github.com/mruby/mruby/pull/6526) Add build config for Luckfox Pico embedded SBC
- [#6528](https://github.com/mruby/mruby/pull/6528) misc: fix spelling
- [#6530](https://github.com/mruby/mruby/pull/6530) Revert "class.c (find_visibility_scope): when callinfo returns, \*ep == NULL; #6512"
- [#6531](https://github.com/mruby/mruby/pull/6531) Improve method table performance by rehashing at 75% load factor
- [#6532](https://github.com/mruby/mruby/pull/6532) Reverted method table optimizations to prioritize memory savings
- [#6533](https://github.com/mruby/mruby/pull/6533) Fix calling `extended` callback
- [#6534](https://github.com/mruby/mruby/pull/6534) Add descriptive comment to mrb_read_float function
- [#6535](https://github.com/mruby/mruby/pull/6535) Added descriptive comments for functions/macros in src/mempool.c
- [#6536](https://github.com/mruby/mruby/pull/6536) Add descriptive comments to public functions in src/debug.c
- [#6537](https://github.com/mruby/mruby/pull/6537) Updated comments in `cdump.c` to remove the `@brief` tag
- [#6539](https://github.com/mruby/mruby/pull/6539) Add descriptive comments for functions in src/load.c
- [#6540](https://github.com/mruby/mruby/pull/6540) Add descriptive comments to MRB_API functions in object.c
- [#6541](https://github.com/mruby/mruby/pull/6541) Add descriptive comments for MRB_API functions in src/array.c
- [#6542](https://github.com/mruby/mruby/pull/6542) Add descriptive comments to MRB_API functions in src/symbol.c
- [#6543](https://github.com/mruby/mruby/pull/6543) Add descriptive comments to several functions in src/dump.c
- [#6544](https://github.com/mruby/mruby/pull/6544) Fix build strings that must be mutable
- [#6545](https://github.com/mruby/mruby/pull/6545) Add descriptive comments for MRB_API functions in src/class.c
- [#6548](https://github.com/mruby/mruby/pull/6548) Add descriptive comments to MRB_API functions in src/etc.c
- [#6549](https://github.com/mruby/mruby/pull/6549) Add descriptive comments to kernel functions
- [#6550](https://github.com/mruby/mruby/pull/6550) Add descriptive comments for MRB_API functions in src/proc.c
- [#6551](https://github.com/mruby/mruby/pull/6551) Add descriptive comments for MRB_API functions in src/state.c
- [#6552](https://github.com/mruby/mruby/pull/6552) Fix: Correct placement of comments in src/variable.c
- [#6553](https://github.com/mruby/mruby/pull/6553) Add descriptive comments for MRB_API functions in src/vm.c
- [#6555](https://github.com/mruby/mruby/pull/6555) `mrb_mt_foreach()` needs to update the pointer at each loop
- [#6556](https://github.com/mruby/mruby/pull/6556) `iv_foreach()` needs to update the pointer at each loop
- [#6560](https://github.com/mruby/mruby/pull/6560) Refactor: Improve Set GC marking and freeing
- [#6561](https://github.com/mruby/mruby/pull/6561) pre-commit updates and fix prettier entrypoint
- [#6562](https://github.com/mruby/mruby/pull/6562) misc: fix spelling word case
- [#6563](https://github.com/mruby/mruby/pull/6563) pre-commit add rubocop with one rule spaces for indentation
- [#6564](https://github.com/mruby/mruby/pull/6564) Remove jumanjihouse pre-commit hooks no longer maintained
- [#6565](https://github.com/mruby/mruby/pull/6565) Rubocop: fix target Ruby version; add two more cops; fix lint error
- [#6566](https://github.com/mruby/mruby/pull/6566) Removed unreferenced variables in `CrossBuild#run_bintest`
- [#6567](https://github.com/mruby/mruby/pull/6567) Avoid array object creation in `cmd_bin` method in bintest
- [#6568](https://github.com/mruby/mruby/pull/6568) mruby-bin-debugger depends on mruby-bin-mrbc in bintest
- [#6569](https://github.com/mruby/mruby/pull/6569) sed s/Mruby/MRuby/g
- [#6571](https://github.com/mruby/mruby/pull/6571) Update limitations.md to add behavior on small hash
- [#6572](https://github.com/mruby/mruby/pull/6572) Add Claude Code GitHub Workflow
- [#6573](https://github.com/mruby/mruby/pull/6573) pre-commit fixes and updates
- [#6574](https://github.com/mruby/mruby/pull/6574) Support initializing structs via keyword arguments
- [#6575](https://github.com/mruby/mruby/pull/6575) Fix typo in file time methods
- [#6581](https://github.com/mruby/mruby/pull/6581) Merge `mrb_obj_iv_inspect()` into `mrb_obj_inspect()`
- [#6582](https://github.com/mruby/mruby/pull/6582) Stricter type tag in `mrb_obj_alloc()`
- [#6583](https://github.com/mruby/mruby/pull/6583) Add fallback to local build_config.rb before using default configuration
- [#6585](https://github.com/mruby/mruby/pull/6585) Fix typo in mruby3.2 docs
- [#6586](https://github.com/mruby/mruby/pull/6586) Makefile: refactor add docs and add command line `help` target
- [#6587](https://github.com/mruby/mruby/pull/6587) Add Set#hash tests
- [#6588](https://github.com/mruby/mruby/pull/6588) Add CodeQL Analysis for GitHub Actions
- [#6589](https://github.com/mruby/mruby/pull/6589) Add pre-commit hook `check-zip-file-is-not-committed`
- [#6591](https://github.com/mruby/mruby/pull/6591) mruby-eval fix license link in README
- [#6593](https://github.com/mruby/mruby/pull/6593) README: Add Contributors Avatars, Star History, Table of Contents
- [#6598](https://github.com/mruby/mruby/pull/6598) Fix heap buffer overflow in `#method_missing`
- [#6599](https://github.com/mruby/mruby/pull/6599) pre-commit: run `markdown-link-check`, `oxipng`, `prettier` manually
- [#6600](https://github.com/mruby/mruby/pull/6600) `dreamcast_shelf build config`: update to use KallistiOS wrappers
- [#6601](https://github.com/mruby/mruby/pull/6601) fix: skip local build_config.rb when working in MRUBY_ROOT
- [#6602](https://github.com/mruby/mruby/pull/6602) Improved iseq annotations for `new` and `!=`
- [#6604](https://github.com/mruby/mruby/pull/6604) pre-commit config updates
- [#6607](https://github.com/mruby/mruby/pull/6607) fix bigint on raspberry pi
- [#6610](https://github.com/mruby/mruby/pull/6610) Extract golden ratio prime into constant
- [#6614](https://github.com/mruby/mruby/pull/6614) Fix uninitialized variable in io_gets causing segmentation fault
- [#6617](https://github.com/mruby/mruby/pull/6617) Fix various minor problems and speed up build
- [#6618](https://github.com/mruby/mruby/pull/6618) Stop generating unnecessary C++ files in mruby-bin-mruby
- [#6621](https://github.com/mruby/mruby/pull/6621) Set up all GEMS before mruby core tasks definition
- [#6624](https://github.com/mruby/mruby/pull/6624) Fixed wrong `MRuby::Build.current` at the top level of `mrbgem.rake`
- [#6628](https://github.com/mruby/mruby/pull/6628) Revert `File.absolute_path` logic
- [#6629](https://github.com/mruby/mruby/pull/6629) pre-commit update
- [#6631](https://github.com/mruby/mruby/pull/6631) Revert "Rakefile: make the whole thing parallel unless SERIAL=1"
- [#6633](https://github.com/mruby/mruby/pull/6633) Fix a heap-buffer-overflow in str strip! methods
- [#6643](https://github.com/mruby/mruby/pull/6643) Fix crash caused by an incorrect node type check in `codegen_masgn`
- [#6651](https://github.com/mruby/mruby/pull/6651) Address stack-use-after-return in the mruby bigint implementation
- [#6653](https://github.com/mruby/mruby/pull/6653) Improve HAL-related components for MinGW
- [#6655](https://github.com/mruby/mruby/pull/6655) Preventing Memory Leaks in `Array#__combination_init`
- [#6656](https://github.com/mruby/mruby/pull/6656) Fix integer overflow in allocation size calculation
- [#6663](https://github.com/mruby/mruby/pull/6663) Added the `kh_is_end()` macro function
- [#6665](https://github.com/mruby/mruby/pull/6665) Fixed use-after-free with `Set#join`
- [#6670](https://github.com/mruby/mruby/pull/6670) Arranging VM dispatch macros
- [#6673](https://github.com/mruby/mruby/pull/6673) Adjust broken license links; clean up Markdown
- [#6677](https://github.com/mruby/mruby/pull/6677) gha: run pre-commit with `--color=always`
- [#6678](https://github.com/mruby/mruby/pull/6678) Put ls-lint and pre-commit in separate workflow files
- [#6679](https://github.com/mruby/mruby/pull/6679) pre-commit autoupdate; update node and prettier
- [#6681](https://github.com/mruby/mruby/pull/6681) Add Cosmopolitan Libc build configuration
- [#6689](https://github.com/mruby/mruby/pull/6689) docs: fix pre-commit manual hooks; fix link
- [#6694](https://github.com/mruby/mruby/pull/6694) Fix mirb build under Cosmopolitan
- [#6695](https://github.com/mruby/mruby/pull/6695) Dependabot: add a cooldown period for new releases
- [#6696](https://github.com/mruby/mruby/pull/6696) Fix parse error with required kwargs and omitted parens
- [#6699](https://github.com/mruby/mruby/pull/6699) Fix mruby-task for PicoRuby Integration
- [#6700](https://github.com/mruby/mruby/pull/6700) Fix float/double pack/unpack on s390x
- [#6706](https://github.com/mruby/mruby/pull/6706) Refactor task class to use symbol IDs
- [#6708](https://github.com/mruby/mruby/pull/6708) `initialize_copy` and `respond_to_missing?` defined as private
- [#6709](https://github.com/mruby/mruby/pull/6709) Add the `MRB_ENSURE()` macro
- [#6711](https://github.com/mruby/mruby/pull/6711) Fix out of bounds read and write in IO.select
- [#6714](https://github.com/mruby/mruby/pull/6714) Fix OP_DEBUG operand type and add NULL check for debug_op_hook
- [#6716](https://github.com/mruby/mruby/pull/6716) Fixes identity for proc object
- [#6717](https://github.com/mruby/mruby/pull/6717) Fix mruby-task: wrapping by critical section and setting initial task receiver
- [#6718](https://github.com/mruby/mruby/pull/6718) Add installation instructions for conda and Homebrew
- [#6723](https://github.com/mruby/mruby/pull/6723) Add `RInteger` and `RFloat` to `RVALUE`
- [#6727](https://github.com/mruby/mruby/pull/6727) Language documentation: update wording of "overloading" section
- [#6729](https://github.com/mruby/mruby/pull/6729) Simplifying dependency addition for gensym task
- [#6730](https://github.com/mruby/mruby/pull/6730) Simplifying presym file generation actions
- [#6733](https://github.com/mruby/mruby/pull/6733) Include `mruby/presym.h` for all source files
- [#6734](https://github.com/mruby/mruby/pull/6734) Chunk array literals at 64 elements to reduce register pressure
- [#6735](https://github.com/mruby/mruby/pull/6735) Prevent full recompilation without changes to presym file
- [#6739](https://github.com/mruby/mruby/pull/6739) Fix MSYS2 build error with drive letters
- [#6743](https://github.com/mruby/mruby/pull/6743) Chunk `%w()` and `%i()` literals to reduce register pressure
- [#6744](https://github.com/mruby/mruby/pull/6744) Raise `NoMatchingPatternError` in `case`/`in` without `else`
- [#6747](https://github.com/mruby/mruby/pull/6747) Correctly handle empty hash as default named argument
- [#6749](https://github.com/mruby/mruby/pull/6749) Fix microcontroller profile
- [#6750](https://github.com/mruby/mruby/pull/6750) Fix out-of-bounds read and divide-by-zero in `Array#product`
- [#6752](https://github.com/mruby/mruby/pull/6752) Fix `attr_reader`-generated methods accepting extra arguments
- [#6753](https://github.com/mruby/mruby/pull/6753) Further optimize `Array#product`
- [#6754](https://github.com/mruby/mruby/pull/6754) Mark `attr_reader` procs as noarg
- [#6755](https://github.com/mruby/mruby/pull/6755) Reload `ci` after `mrb_hash_delete_key()` in keyword argument handling
- [#6756](https://github.com/mruby/mruby/pull/6756) Avoid impact of object modifications caused by `mrb_vm_exec()` calls
- [#6758](https://github.com/mruby/mruby/pull/6758) Don't assign result of `mrb_funcall()` directly to `regs`
- [#6759](https://github.com/mruby/mruby/pull/6759) Define `mrb_bigint_p()` always
- [#6761](https://github.com/mruby/mruby/pull/6761) Fix `mrb_gc_unregister()` to remove all matching entries
- [#6762](https://github.com/mruby/mruby/pull/6762) Write generated test C files atomically to avoid build race condition
- [#6765](https://github.com/mruby/mruby/pull/6765) Fix `Lazy#flat_map` to handle non-enumerable block return values
- [#6767](https://github.com/mruby/mruby/pull/6767) Allow compound statement in parenthesized argument context
- [#6780](https://github.com/mruby/mruby/pull/6780) Fix `String#prepend` with self-referencing arguments
- [#6781](https://github.com/mruby/mruby/pull/6781) Protect `sprintf` format string from mutation during callbacks
- [#6783](https://github.com/mruby/mruby/pull/6783) Pin GitHub Actions workflows to commit hashes
# Security Fixes
- Buffer overflow in bigint uadd ([3f2611e](https://github.com/mruby/mruby/commit/3f2611e))
- Stack buffer overflow in Montgomery reduction ([edce0a3](https://github.com/mruby/mruby/commit/edce0a3))
- Buffer overflow in pack_uu encoding ([2993302](https://github.com/mruby/mruby/commit/2993302))
- Buffer overflow in IO#ungetc ([01ab2ff](https://github.com/mruby/mruby/commit/01ab2ff))
- Heap-buffer-overflow in pattern alternation codegen ([eea9e30](https://github.com/mruby/mruby/commit/eea9e30))
- Out of bounds read and write in IO.select ([44831711](https://github.com/mruby/mruby/commit/44831711))
- Off-by-one in bounds check for symbol names and pool strings in load.c ([b3b8c01](https://github.com/mruby/mruby/commit/b3b8c01))
- Use-after-free in Set operations ([a6b55e7](https://github.com/mruby/mruby/commit/a6b55e7))
- Use-after-free in Array set operations ([729b84c](https://github.com/mruby/mruby/commit/729b84c))
- Use-after-free in Set#join ([0e653eb](https://github.com/mruby/mruby/commit/0e653eb))
- Use-after-realloc in Array#sort! ([eb39897](https://github.com/mruby/mruby/commit/eb39897))
- Heap-use-after-free in insertion_sort ([099d2c47](https://github.com/mruby/mruby/commit/099d2c47))
- Integer overflow in str_check_length ([6afff1c3](https://github.com/mruby/mruby/commit/6afff1c3))
- Integer overflow in Integer#lcm ([070bef24](https://github.com/mruby/mruby/commit/070bef24))
- Heap buffer overflow in `#method_missing` ([550d10a](https://github.com/mruby/mruby/commit/550d10a))
- Out-of-bounds read and divide-by-zero in `Array#product` ([8441eaf](https://github.com/mruby/mruby/commit/8441eaf))
- Heap buffer overflow in `String#prepend` with self-referencing arguments ([18ba026](https://github.com/mruby/mruby/commit/18ba026))
- Use-after-free in `sprintf` via `to_s` callback mutating format string ([48fc422](https://github.com/mruby/mruby/commit/48fc422))
- Multiple memory leak fixes in bigint, Set, Array, and Task gems
+11 -4
View File
@@ -138,8 +138,8 @@
/* turn off generational GC by default */ /* turn off generational GC by default */
//#define MRB_GC_TURN_OFF_GENERATIONAL //#define MRB_GC_TURN_OFF_GENERATIONAL
/* default size of khash table bucket */ /* initial size of khash table bucket */
//#define KHASH_DEFAULT_SIZE 32 //#define KHASH_INITIAL_SIZE 32
/* allocated memory address alignment */ /* allocated memory address alignment */
//#define POOL_ALIGNMENT 4 //#define POOL_ALIGNMENT 4
@@ -172,6 +172,13 @@
#define MRB_SYMBOL_LINEAR_THRESHOLD 256 #define MRB_SYMBOL_LINEAR_THRESHOLD 256
#endif #endif
/* Maximum number of dynamic symbols (created at runtime via to_sym etc.)
Presyms, inline symbols, and mrb_intern_static symbols are excluded.
Set to 0 to disable the limit. */
#ifndef MRB_SYMBOL_MAX
#define MRB_SYMBOL_MAX 4096
#endif
/* obsolete configurations */ /* obsolete configurations */
#if defined(DISABLE_STDIO) || defined(MRB_DISABLE_STDIO) #if defined(DISABLE_STDIO) || defined(MRB_DISABLE_STDIO)
# define MRB_NO_STDIO # define MRB_NO_STDIO
@@ -208,8 +215,8 @@
# define MRB_NO_METHOD_CACHE # define MRB_NO_METHOD_CACHE
# endif # endif
# ifndef KHASH_DEFAULT_SIZE # ifndef KHASH_INITIAL_SIZE
# define KHASH_DEFAULT_SIZE 16 # define KHASH_INITIAL_SIZE 16
# endif # endif
# ifndef MRB_HEAP_PAGE_SIZE # ifndef MRB_HEAP_PAGE_SIZE
+54 -9
View File
@@ -113,6 +113,8 @@
#include "mrbconf.h" #include "mrbconf.h"
typedef struct mrb_state mrb_state;
#include <mruby/common.h> #include <mruby/common.h>
#include <mruby/value.h> #include <mruby/value.h>
#include <mruby/gc.h> #include <mruby/gc.h>
@@ -156,8 +158,6 @@ typedef uint32_t mrb_aspec;
typedef struct mrb_irep mrb_irep; typedef struct mrb_irep mrb_irep;
struct mrb_state;
#ifndef MRB_FIXED_STATE_ATEXIT_STACK_SIZE #ifndef MRB_FIXED_STATE_ATEXIT_STACK_SIZE
#define MRB_FIXED_STATE_ATEXIT_STACK_SIZE 5 #define MRB_FIXED_STATE_ATEXIT_STACK_SIZE 5
#endif #endif
@@ -224,7 +224,7 @@ mrb_static_assert_powerof2(MRB_METHOD_CACHE_SIZE);
* @param self The self object * @param self The self object
* @return [mrb_value] The function's return value * @return [mrb_value] The function's return value
*/ */
typedef mrb_value (*mrb_func_t)(struct mrb_state *mrb, mrb_value self); typedef mrb_value (*mrb_func_t)(mrb_state *mrb, mrb_value self);
typedef struct { typedef struct {
uint32_t flags; /* method flags (no symbol packed) */ uint32_t flags; /* method flags (no symbol packed) */
@@ -243,9 +243,26 @@ struct mrb_cache_entry {
}; };
#endif #endif
#ifdef MRB_CONST_CACHE_SIZE
# undef MRB_NO_CONST_CACHE
mrb_static_assert_powerof2(MRB_CONST_CACHE_SIZE);
#else
/* default constant cache size: 64 */
/* cache size needs to be power of 2 */
# define MRB_CONST_CACHE_SIZE (1<<6)
#endif
#ifndef MRB_NO_CONST_CACHE
struct mrb_const_cache_entry {
const struct mrb_irep *irep;
mrb_sym sym;
mrb_value value;
};
#endif
struct mrb_jmpbuf; struct mrb_jmpbuf;
typedef void (*mrb_atexit_func)(struct mrb_state*); typedef void (*mrb_atexit_func)(mrb_state*);
#ifdef MRB_USE_TASK_SCHEDULER #ifdef MRB_USE_TASK_SCHEDULER
struct mrb_task; struct mrb_task;
@@ -257,10 +274,12 @@ typedef struct mrb_task_state {
volatile mrb_bool switching; /* Context switch pending flag */ volatile mrb_bool switching; /* Context switch pending flag */
struct mrb_task *main_task; /* Main task wrapper for root context */ struct mrb_task *main_task; /* Main task wrapper for root context */
uint8_t scheduler_lock; /* Lock counter for synchronous execution */ uint8_t scheduler_lock; /* Lock counter for synchronous execution */
mrb_bool loop_running; /* Active mrb_task_run loop flag */
mrb_bool exception_as_result; /* Return unhandled task exceptions as values */
} mrb_task_state; } mrb_task_state;
#endif #endif
typedef struct mrb_state { struct mrb_state {
struct mrb_jmpbuf *jmp; struct mrb_jmpbuf *jmp;
struct mrb_context *c; struct mrb_context *c;
@@ -297,22 +316,28 @@ typedef struct mrb_state {
struct mrb_cache_entry cache[MRB_METHOD_CACHE_SIZE]; struct mrb_cache_entry cache[MRB_METHOD_CACHE_SIZE];
#endif #endif
#ifndef MRB_NO_CONST_CACHE
struct mrb_const_cache_entry const_cache[MRB_CONST_CACHE_SIZE];
#endif
mrb_sym symidx; mrb_sym symidx;
const char **symtbl; const char **symtbl;
uint8_t *sym_flags; /* per-symbol flags (SYM_FL_*) */
size_t symcapa; size_t symcapa;
struct mrb_sym_hash_table *symhash; struct mrb_sym_hash_table *symhash;
void *sym_pool; void *sym_pool;
mrb_sym dynamic_sym_count; /* count of dynamic (GC-candidate) symbols */
#ifndef MRB_USE_ALL_SYMBOLS #ifndef MRB_USE_ALL_SYMBOLS
char symbuf[8]; /* buffer for small symbol names */ char symbuf[8]; /* buffer for small symbol names */
#endif #endif
#ifdef MRB_USE_DEBUG_HOOK #ifdef MRB_USE_DEBUG_HOOK
void (*code_fetch_hook)(struct mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs); void (*code_fetch_hook)(mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs);
void (*debug_op_hook)(struct mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs); void (*debug_op_hook)(mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs);
#endif #endif
#ifdef MRB_BYTECODE_DECODE_OPTION #ifdef MRB_BYTECODE_DECODE_OPTION
mrb_code (*bytecode_decoder)(struct mrb_state* mrb, mrb_code code); mrb_code (*bytecode_decoder)(mrb_state* mrb, mrb_code code);
#endif #endif
struct RClass *eException_class; struct RClass *eException_class;
@@ -339,7 +364,7 @@ typedef struct mrb_state {
#ifdef MRB_USE_TASK_SCHEDULER #ifdef MRB_USE_TASK_SCHEDULER
mrb_task_state task; /* Task scheduler state */ mrb_task_state task; /* Task scheduler state */
#endif #endif
} mrb_state; };
/** /**
* Defines a new class. * Defines a new class.
@@ -1159,6 +1184,21 @@ MRB_API mrb_value mrb_funcall_id(mrb_state *mrb, mrb_value val, mrb_sym mid, mrb
* @see mrb_funcall * @see mrb_funcall
*/ */
MRB_API mrb_value mrb_funcall_argv(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_int argc, const mrb_value *argv); MRB_API mrb_value mrb_funcall_argv(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_int argc, const mrb_value *argv);
/*
* Convenience wrappers for `mrb_funcall_argv` with a fixed argument count.
* Avoids the 16-slot fixed argv buffer used by the variadic `mrb_funcall_id`.
*/
MRB_INLINE mrb_value
mrb_funcall_argv1(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_value a1)
{
return mrb_funcall_argv(mrb, val, name, 1, &a1);
}
MRB_INLINE mrb_value
mrb_funcall_argv2(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_value a1, mrb_value a2)
{
const mrb_value argv[] = { a1, a2 };
return mrb_funcall_argv(mrb, val, name, 2, argv);
}
/** /**
* Call existing Ruby functions with a block. * Call existing Ruby functions with a block.
*/ */
@@ -1284,6 +1324,11 @@ MRB_API void mrb_method_cache_clear(mrb_state *mrb);
#else #else
#define mrb_method_cache_clear(mrb) ((void)0) #define mrb_method_cache_clear(mrb) ((void)0)
#endif #endif
#ifndef MRB_NO_CONST_CACHE
MRB_API void mrb_const_cache_clear(mrb_state *mrb);
#else
#define mrb_const_cache_clear(mrb) ((void)0)
#endif
/** /**
* Check if mrb_open() failed * Check if mrb_open() failed
+24 -2
View File
@@ -20,7 +20,17 @@ typedef struct mrb_shared_array {
mrb_value *ptr; mrb_value *ptr;
} mrb_shared_array; } mrb_shared_array;
#if defined(MRB_32BIT) && defined(MRB_NO_BOXING) && (!defined(MRB_USE_FLOAT32) || defined(MRB_INT64)) && !defined(MRB_ARY_NO_EMBED) /* On 32-bit platforms whose ABI gives 8-byte members 8-byte alignment
(ARM, MIPS, xtensa, ...), an embedded mrb_value array forces 8-byte
alignment of the inner union, padding the heap-form layout and
inflating struct size past the 5-word RVALUE limit. Disable embedding
whenever mrb_value contains an 8-byte aligned member: nan-boxing
(uint64_t), or no-boxing with int64_t/double inside the union. */
#if defined(MRB_32BIT) && \
(defined(MRB_NAN_BOXING) || \
(defined(MRB_NO_BOXING) && \
(!defined(MRB_USE_FLOAT32) || defined(MRB_INT64)))) && \
!defined(MRB_ARY_NO_EMBED)
# define MRB_ARY_NO_EMBED # define MRB_ARY_NO_EMBED
#endif #endif
@@ -57,7 +67,7 @@ struct RArray {
#define ARY_UNSET_EMBED_FLAG(a) (void)0 #define ARY_UNSET_EMBED_FLAG(a) (void)0
#define ARY_EMBED_LEN(a) 0 #define ARY_EMBED_LEN(a) 0
#define ARY_SET_EMBED_LEN(a,len) (void)0 #define ARY_SET_EMBED_LEN(a,len) (void)0
#define ARY_EMBED_PTR(a) 0 #define ARY_EMBED_PTR(a) ((mrb_value*)NULL)
#else #else
#define MRB_ARY_EMBED_MASK 7 #define MRB_ARY_EMBED_MASK 7
#define ARY_EMBED_P(a) ((a)->flags & MRB_ARY_EMBED_MASK) #define ARY_EMBED_P(a) ((a)->flags & MRB_ARY_EMBED_MASK)
@@ -71,6 +81,7 @@ struct RArray {
#define ARY_PTR(a) (ARY_EMBED_P(a)?ARY_EMBED_PTR(a):(a)->as.heap.ptr) #define ARY_PTR(a) (ARY_EMBED_P(a)?ARY_EMBED_PTR(a):(a)->as.heap.ptr)
#define RARRAY_LEN(a) ARY_LEN(RARRAY(a)) #define RARRAY_LEN(a) ARY_LEN(RARRAY(a))
#define RARRAY_PTR(a) ARY_PTR(RARRAY(a)) #define RARRAY_PTR(a) ARY_PTR(RARRAY(a))
#define RARRAY_GETMEM(a, ptr, len) ARY_GETMEM(RARRAY(a), ptr, len)
#define ARY_SET_LEN(a,n) do {\ #define ARY_SET_LEN(a,n) do {\
if (ARY_EMBED_P(a)) {\ if (ARY_EMBED_P(a)) {\
mrb_assert((n) <= MRB_ARY_EMBED_LEN_MAX); \ mrb_assert((n) <= MRB_ARY_EMBED_LEN_MAX); \
@@ -84,6 +95,17 @@ struct RArray {
#define ARY_SHARED_P(a) ((a)->flags & MRB_ARY_SHARED) #define ARY_SHARED_P(a) ((a)->flags & MRB_ARY_SHARED)
#define ARY_SET_SHARED_FLAG(a) ((a)->flags |= MRB_ARY_SHARED) #define ARY_SET_SHARED_FLAG(a) ((a)->flags |= MRB_ARY_SHARED)
#define ARY_UNSET_SHARED_FLAG(a) ((a)->flags &= ~MRB_ARY_SHARED) #define ARY_UNSET_SHARED_FLAG(a) ((a)->flags &= ~MRB_ARY_SHARED)
#define ARY_GETMEM(a, ptr, len) do { \
struct RArray *MRB_UNIQNAME(_a_) = (a); \
if (ARY_EMBED_P(MRB_UNIQNAME(_a_))) { \
(len) = ARY_EMBED_LEN(MRB_UNIQNAME(_a_)); \
(ptr) = ARY_EMBED_PTR(MRB_UNIQNAME(_a_)); \
} \
else { \
(len) = MRB_UNIQNAME(_a_)->as.heap.len; \
(ptr) = MRB_UNIQNAME(_a_)->as.heap.ptr; \
} \
} while (0)
MRB_API void mrb_ary_modify(mrb_state*, struct RArray*); MRB_API void mrb_ary_modify(mrb_state*, struct RArray*);
MRB_API mrb_value mrb_ary_dup(mrb_state*, mrb_value ary); MRB_API mrb_value mrb_ary_dup(mrb_state*, mrb_value ary);
+3 -2
View File
@@ -103,7 +103,8 @@ mrb_unboxed_type(mrb_value o)
{ {
if (!mrb_float_p(o) && mrb_nb_tt(o) == MRB_NANBOX_TT_OBJECT && o.u != 0) { if (!mrb_float_p(o) && mrb_nb_tt(o) == MRB_NANBOX_TT_OBJECT && o.u != 0) {
return ((struct RBasic*)(uintptr_t)o.u)->tt; return ((struct RBasic*)(uintptr_t)o.u)->tt;
} else { }
else {
return MRB_TT_FALSE; return MRB_TT_FALSE;
} }
} }
@@ -150,7 +151,7 @@ mrb_nan_boxing_value_int(mrb_value v)
#define SET_TRUE_VALUE(r) NANBOX_SET_MISC_VALUE(r, MRB_TT_TRUE, 1) #define SET_TRUE_VALUE(r) NANBOX_SET_MISC_VALUE(r, MRB_TT_TRUE, 1)
#define SET_BOOL_VALUE(r,b) NANBOX_SET_MISC_VALUE(r, (b) ? MRB_TT_TRUE : MRB_TT_FALSE, 1) #define SET_BOOL_VALUE(r,b) NANBOX_SET_MISC_VALUE(r, (b) ? MRB_TT_TRUE : MRB_TT_FALSE, 1)
#ifdef MRB_INT64 #ifdef MRB_INT64
MRB_API mrb_value mrb_boxing_int_value(struct mrb_state*, mrb_int); MRB_API mrb_value mrb_boxing_int_value(mrb_state*, mrb_int);
#define SET_INT_VALUE(mrb, r, n) ((r) = mrb_boxing_int_value(mrb, n)) #define SET_INT_VALUE(mrb, r, n) ((r) = mrb_boxing_int_value(mrb, n))
#else #else
#define SET_INT_VALUE(mrb, r, n) SET_FIXNUM_VALUE(r, n) #define SET_INT_VALUE(mrb, r, n) SET_FIXNUM_VALUE(r, n)
+7 -5
View File
@@ -165,11 +165,11 @@ mrb_val_union(mrb_value v)
return x; return x;
} }
MRB_API mrb_value mrb_word_boxing_cptr_value(struct mrb_state*, void*); MRB_API mrb_value mrb_word_boxing_cptr_value(mrb_state*, void*);
#ifndef MRB_NO_FLOAT #ifndef MRB_NO_FLOAT
MRB_API mrb_value mrb_word_boxing_float_value(struct mrb_state*, mrb_float); MRB_API mrb_value mrb_word_boxing_float_value(mrb_state*, mrb_float);
#endif #endif
MRB_API mrb_value mrb_boxing_int_value(struct mrb_state*, mrb_int); MRB_API mrb_value mrb_boxing_int_value(mrb_state*, mrb_int);
#if WORDBOX_IMMEDIATE_MASK == 0x3 #if WORDBOX_IMMEDIATE_MASK == 0x3
#define mrb_immediate_p(o) ((o).w & WORDBOX_IMMEDIATE_MASK || (o).w <= MRB_Qundef) #define mrb_immediate_p(o) ((o).w & WORDBOX_IMMEDIATE_MASK || (o).w <= MRB_Qundef)
@@ -266,9 +266,11 @@ mrb_unboxed_type(mrb_value o)
{ {
if (mrb_nil_p(o)) { if (mrb_nil_p(o)) {
return MRB_TT_FALSE; return MRB_TT_FALSE;
} else if ((o.w & WORDBOX_IMMEDIATE_MASK) == 0) { }
else if ((o.w & WORDBOX_IMMEDIATE_MASK) == 0) {
return mrb_val_union(o).bp->tt; return mrb_val_union(o).bp->tt;
} else { }
else {
return MRB_TT_FALSE; return MRB_TT_FALSE;
} }
} }
+6 -3
View File
@@ -100,10 +100,13 @@ void mrb_mc_clear_by_class(mrb_state *mrb, struct RClass* c);
typedef int (mrb_mt_foreach_func)(mrb_state*,mrb_sym,mrb_method_t,void*); typedef int (mrb_mt_foreach_func)(mrb_state*,mrb_sym,mrb_method_t,void*);
MRB_API void mrb_mt_foreach(mrb_state*, struct RClass*, mrb_mt_foreach_func*, void*); MRB_API void mrb_mt_foreach(mrb_state*, struct RClass*, mrb_mt_foreach_func*, void*);
/* ROM method table types for static method registration */ /* ROM method table types for static method registration.
* NOTE: `func` is kept as the first union member so that positional
* aggregate initialization in MRB_MT_ENTRY works without C99
* designated initializers (required for legacy C++ compilers). */
union mrb_mt_ptr { union mrb_mt_ptr {
const struct RProc *proc;
mrb_func_t func; mrb_func_t func;
const struct RProc *proc;
}; };
/* entry combining function pointer, symbol key, and flags */ /* entry combining function pointer, symbol key, and flags */
@@ -128,7 +131,7 @@ typedef struct mrb_mt_tbl {
/* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with MRB_MT_PRIVATE. */ /* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with MRB_MT_PRIVATE. */
#define MRB_MT_ENTRY(fn, sym, flags) \ #define MRB_MT_ENTRY(fn, sym, flags) \
{ { .func = (fn) }, (sym), (flags) | MRB_MT_FUNC } { { (fn) }, (sym), (flags) | MRB_MT_FUNC }
#define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff)) #define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff))
/* "removed" tombstone: MRB_MT_FUNC flag set with NULL function pointer. /* "removed" tombstone: MRB_MT_FUNC flag set with NULL function pointer.
+1
View File
@@ -134,6 +134,7 @@ struct mrb_parser_state {
mrb_sym* filename_table; mrb_sym* filename_table;
uint16_t filename_table_length; uint16_t filename_table_length;
uint16_t current_filename_index; uint16_t current_filename_index;
uint16_t prev_file_lineno; /* saved lineno before partial_hook file switch */
/* Variable-sized node management */ /* Variable-sized node management */
mrb_ast_node *nvars; mrb_ast_node *nvars;
+27 -6
View File
@@ -37,7 +37,14 @@ MRB_API mrb_value mrb_exc_new_str(mrb_state *mrb, struct RClass* c, mrb_value st
#define mrb_exc_new_lit(mrb, c, lit) mrb_exc_new_str(mrb, c, mrb_str_new_lit(mrb, lit)) #define mrb_exc_new_lit(mrb, c, lit) mrb_exc_new_str(mrb, c, mrb_str_new_lit(mrb, lit))
MRB_API mrb_noreturn void mrb_no_method_error(mrb_state *mrb, mrb_sym id, mrb_value args, const char *fmt, ...); MRB_API mrb_noreturn void mrb_no_method_error(mrb_state *mrb, mrb_sym id, mrb_value args, const char *fmt, ...);
#if defined(MRB_NAN_BOXING) || defined(MRB_WORD_BOXING) || defined(MRB_64BIT) /* On 32-bit platforms whose ABI gives uint64_t/double 8-byte alignment
(ARM, MIPS, PowerPC, xtensa, ...), embedding mrb_value directly in
RBreak forces 8-byte alignment that pushes the struct past the 5-word
RVALUE budget via padding. Store the value bits as a uint32_t array
(4-byte aligned) to dodge that padding. Word-boxing's mrb_value is
just a uintptr_t with no over-alignment, and 64-bit platforms have no
alignment gap to begin with, so neither needs the workaround. */
#if defined(MRB_64BIT) || defined(MRB_WORD_BOXING)
#undef MRB_USE_RBREAK_VALUE_UNION #undef MRB_USE_RBREAK_VALUE_UNION
#else #else
#define MRB_USE_RBREAK_VALUE_UNION 1 #define MRB_USE_RBREAK_VALUE_UNION 1
@@ -45,7 +52,8 @@ MRB_API mrb_noreturn void mrb_no_method_error(mrb_state *mrb, mrb_sym id, mrb_va
/* /*
* flags: * flags:
* 0..7: enum mrb_vtype (only when defined MRB_USE_RBREAK_VALUE_UNION) * 0..7: enum mrb_vtype (only when MRB_USE_RBREAK_VALUE_UNION and
* !MRB_NAN_BOXING; nan-boxing encodes the type in the bits)
* 8..10: RBREAK_TAGs in src/vm.c (otherwise, set to 0) * 8..10: RBREAK_TAGs in src/vm.c (otherwise, set to 0)
*/ */
struct RBreak { struct RBreak {
@@ -53,11 +61,11 @@ struct RBreak {
uintptr_t ci_break_index; // The top-level ci index to break. One before the return destination. uintptr_t ci_break_index; // The top-level ci index to break. One before the return destination.
#ifndef MRB_USE_RBREAK_VALUE_UNION #ifndef MRB_USE_RBREAK_VALUE_UNION
mrb_value val; mrb_value val;
#elif defined(MRB_NAN_BOXING)
/* nan-boxing: mrb_value is a single 64-bit word (type encoded in NaN bits) */
uint32_t value[sizeof(mrb_value) / sizeof(uint32_t)];
#else #else
/* Store value as uint32_t words instead of union mrb_value_union /* no-boxing: store only the union bits; tt goes in flags */
to avoid 8-byte alignment of int64_t/double on 32-bit platforms
(e.g., ARM, MIPS, PowerPC) which would inflate struct size beyond
the 5-word RVALUE limit due to padding. */
uint32_t value[sizeof(union mrb_value_union) / sizeof(uint32_t)]; uint32_t value[sizeof(union mrb_value_union) / sizeof(uint32_t)];
#endif #endif
}; };
@@ -65,6 +73,19 @@ struct RBreak {
#ifndef MRB_USE_RBREAK_VALUE_UNION #ifndef MRB_USE_RBREAK_VALUE_UNION
#define mrb_break_value_get(brk) ((brk)->val) #define mrb_break_value_get(brk) ((brk)->val)
#define mrb_break_value_set(brk, v) ((brk)->val = v) #define mrb_break_value_set(brk, v) ((brk)->val = v)
#elif defined(MRB_NAN_BOXING)
static inline mrb_value
mrb_break_value_get(struct RBreak *brk)
{
mrb_value val;
memcpy(&val, brk->value, sizeof(val));
return val;
}
static inline void
mrb_break_value_set(struct RBreak *brk, mrb_value val)
{
memcpy(brk->value, &val, sizeof(val));
}
#else #else
#define RBREAK_VALUE_TT_MASK ((1 << 8) - 1) #define RBREAK_VALUE_TT_MASK ((1 << 8) - 1)
static inline mrb_value static inline mrb_value
+15 -9
View File
@@ -14,15 +14,12 @@
*/ */
MRB_BEGIN_DECL MRB_BEGIN_DECL
struct mrb_state;
#define MRB_EACH_OBJ_OK 0 #define MRB_EACH_OBJ_OK 0
#define MRB_EACH_OBJ_BREAK 1 #define MRB_EACH_OBJ_BREAK 1
typedef int (mrb_each_object_callback)(struct mrb_state *mrb, struct RBasic *obj, void *data); typedef int (mrb_each_object_callback)(mrb_state *mrb, struct RBasic *obj, void *data);
void mrb_objspace_each_objects(struct mrb_state *mrb, mrb_each_object_callback *callback, void *data); void mrb_objspace_each_objects(mrb_state *mrb, mrb_each_object_callback *callback, void *data);
size_t mrb_objspace_page_slot_size(void); size_t mrb_objspace_page_slot_size(void);
MRB_API void mrb_free_context(struct mrb_state *mrb, struct mrb_context *c); MRB_API void mrb_free_context(mrb_state *mrb, struct mrb_context *c);
#ifndef MRB_GC_ARENA_SIZE #ifndef MRB_GC_ARENA_SIZE
#define MRB_GC_ARENA_SIZE 100 #define MRB_GC_ARENA_SIZE 100
@@ -48,7 +45,7 @@ typedef struct mrb_gc {
mrb_bool gray_overflow:1; /* gray stack overflowed; needs heap rescan */ mrb_bool gray_overflow:1; /* gray stack overflowed; needs heap rescan */
size_t live; /* count of live objects */ size_t live; /* count of live objects */
size_t live_after_mark; /* old generation objects */ size_t live_after_mark; /* old generation objects */
size_t threshold; /* threshold to start GC */ mrb_int gc_debt; /* <0:credit, >0:needs GC */
size_t oldgen_threshold; /* threshold to kick major GC */ size_t oldgen_threshold; /* threshold to kick major GC */
mrb_gc_state state; /* current state of gc */ mrb_gc_state state; /* current state of gc */
int interval_ratio; int interval_ratio;
@@ -59,6 +56,9 @@ typedef struct mrb_gc {
mrb_bool generational :1; /* generational GC mode */ mrb_bool generational :1; /* generational GC mode */
mrb_bool full :1; /* major GC mode */ mrb_bool full :1; /* major GC mode */
mrb_bool out_of_memory :1; /* out-of-memory error occurred */ mrb_bool out_of_memory :1; /* out-of-memory error occurred */
size_t step_limit; /* 0=unlimited, >0=absolute step cap */
size_t malloc_increase; /* malloc bytes since last GC cycle */
size_t malloc_threshold; /* 0=disabled, >0=bytes to trigger GC */
#ifdef MRB_GC_FIXED_ARENA #ifdef MRB_GC_FIXED_ARENA
struct RBasic *arena[MRB_GC_ARENA_SIZE]; /* GC protection array */ struct RBasic *arena[MRB_GC_ARENA_SIZE]; /* GC protection array */
@@ -67,10 +67,16 @@ typedef struct mrb_gc {
int arena_capa; /* size of protection array */ int arena_capa; /* size of protection array */
#endif #endif
int arena_idx; int arena_idx;
#ifdef MRB_GC_STATS
uint32_t gc_total_count; /* total GC invocations */
uint32_t minor_gc_count; /* minor GC count */
uint32_t major_gc_count; /* major GC count */
#endif
} mrb_gc; } mrb_gc;
MRB_API mrb_bool mrb_object_dead_p(struct mrb_state *mrb, struct RBasic *object); MRB_API mrb_bool mrb_object_dead_p(mrb_state *mrb, struct RBasic *object);
MRB_API int mrb_gc_add_region(struct mrb_state *mrb, void *start, size_t size); MRB_API int mrb_gc_add_region(mrb_state *mrb, void *start, size_t size);
#define MRB_GC_RED 7 #define MRB_GC_RED 7
+3
View File
@@ -279,4 +279,7 @@ mrb_value mrb_bint_abs(mrb_state *mrb, mrb_value x);
void mrb_task_mark_all(mrb_state *mrb); void mrb_task_mark_all(mrb_state *mrb);
#endif #endif
/* Internal object allocation without type validation (gc.c) */
struct RBasic* mrb_obj_alloc_core(mrb_state*, enum mrb_vtype, struct RClass*);
#endif /* MRUBY_INTERNAL_H */ #endif /* MRUBY_INTERNAL_H */
+1 -1
View File
@@ -20,7 +20,7 @@ enum irep_pool_type {
IREP_TT_SSTR = 2, /* string (static) */ IREP_TT_SSTR = 2, /* string (static) */
IREP_TT_INT32 = 1, /* 32-bit integer */ IREP_TT_INT32 = 1, /* 32-bit integer */
IREP_TT_INT64 = 3, /* 64-bit integer */ IREP_TT_INT64 = 3, /* 64-bit integer */
IREP_TT_BIGINT = 7, /* big integer (not yet supported) */ IREP_TT_BIGINT = 7, /* big integer */
IREP_TT_FLOAT = 5, /* float (double/float) */ IREP_TT_FLOAT = 5, /* float (double/float) */
}; };
+1 -1
View File
@@ -21,7 +21,7 @@ typedef uint32_t khint_t;
typedef khint_t khiter_t; typedef khint_t khiter_t;
#ifndef KHASH_INITIAL_SIZE #ifndef KHASH_INITIAL_SIZE
# define KHASH_INITIAL_SIZE 8 # define KHASH_INITIAL_SIZE 32
#endif #endif
#define KHASH_MIN_SIZE 8 #define KHASH_MIN_SIZE 8
#define KHASH_SMALL_LIMIT 4 #define KHASH_SMALL_LIMIT 4
+54
View File
@@ -102,6 +102,60 @@ struct RProc {
#define MRB_PROC_ALIAS 8192 #define MRB_PROC_ALIAS 8192
#define MRB_PROC_ALIAS_P(p) (((p)->flags & MRB_PROC_ALIAS) != 0) #define MRB_PROC_ALIAS_P(p) (((p)->flags & MRB_PROC_ALIAS) != 0)
/* Compressed aspec for cfunc procs (13 bits in RProc.flags).
* Uses free bits 0-6 and 14-19 to store a compressed argument spec.
* Layout: block(0) kdict(1) key(2-3) post(4-5) rest(6) opt(14-16) req(17-19)
* Field widths are smaller than the full 24-bit aspec: req/opt max 7, post/key max 3.
* Values exceeding the compressed range are clamped and rest is forced to 1. */
#define MRB_PROC_CASPEC_MASK 0xfc07fu /* bits 0-6 and 14-19 */
static inline uint32_t
mrb_proc_compress_aspec(mrb_aspec aspec)
{
uint32_t req = MRB_ASPEC_REQ(aspec);
uint32_t opt = MRB_ASPEC_OPT(aspec);
uint32_t rest = MRB_ASPEC_REST(aspec);
uint32_t post = MRB_ASPEC_POST(aspec);
uint32_t key = MRB_ASPEC_KEY(aspec);
uint32_t kdict = MRB_ASPEC_KDICT(aspec);
uint32_t block = MRB_ASPEC_BLOCK(aspec);
if (req > 7 || opt > 7 || post > 3 || key > 3) {
if (req > 7) req = 7;
if (opt > 7) opt = 7;
if (post > 3) post = 3;
if (key > 3) key = 3;
rest = 1;
}
return block | (kdict << 1) | (key << 2) | (post << 4) | (rest << 6)
| (opt << 14) | (req << 17);
}
static inline mrb_aspec
mrb_proc_decompress_caspec(uint32_t flags)
{
return (((flags >> 17) & 0x7) << 18) /* req */
| (((flags >> 14) & 0x7) << 13) /* opt */
| (((flags >> 6) & 0x1) << 12) /* rest */
| (((flags >> 4) & 0x3) << 7) /* post */
| (((flags >> 2) & 0x3) << 2) /* key */
| (((flags >> 1) & 0x1) << 1) /* kdict */
| (flags & 0x1); /* block */
}
static inline void
mrb_proc_set_cfunc_aspec(struct RProc *p, mrb_aspec aspec)
{
p->flags &= ~(MRB_PROC_NOARG | MRB_PROC_CASPEC_MASK);
if (aspec == 0) {
p->flags |= MRB_PROC_NOARG;
}
else {
p->flags |= mrb_proc_compress_aspec(aspec);
}
}
#define mrb_proc_ptr(v) ((struct RProc*)(mrb_ptr(v))) #define mrb_proc_ptr(v) ((struct RProc*)(mrb_ptr(v)))
struct RProc *mrb_proc_new(mrb_state*, const mrb_irep*); struct RProc *mrb_proc_new(mrb_state*, const mrb_irep*);
+10
View File
@@ -342,6 +342,16 @@ MRB_API const char *mrb_string_value_cstr(mrb_state *mrb, mrb_value *str);
*/ */
MRB_API mrb_value mrb_str_dup(mrb_state *mrb, mrb_value str); MRB_API mrb_value mrb_str_dup(mrb_state *mrb, mrb_value str);
/**
* Returns a frozen string object.
* The string will be duplicated and frozen if it is not already frozen.
*
* @param mrb The current mruby state.
* @param str An original Ruby string.
* @return [mrb_value] Ruby frozen string.
*/
MRB_API mrb_value mrb_str_dup_frozen(mrb_state *mrb, mrb_value str);
/** /**
* Returns a symbol from a passed in Ruby string. * Returns a symbol from a passed in Ruby string.
* *
+7 -5
View File
@@ -55,8 +55,6 @@ typedef uint8_t mrb_bool;
# endif # endif
#endif #endif
struct mrb_state;
#if defined _MSC_VER && _MSC_VER < 1800 #if defined _MSC_VER && _MSC_VER < 1800
# define PRIo64 "llo" # define PRIo64 "llo"
# define PRId64 "lld" # define PRId64 "lld"
@@ -316,7 +314,11 @@ struct RCptr {
#endif #endif
#define mrb_test(o) mrb_bool(o) #define mrb_test(o) mrb_bool(o)
#ifndef mrb_bigint_p #ifndef mrb_bigint_p
#ifdef MRB_USE_BIGINT
#define mrb_bigint_p(o) (mrb_type(o) == MRB_TT_BIGINT) #define mrb_bigint_p(o) (mrb_type(o) == MRB_TT_BIGINT)
#else
#define mrb_bigint_p(o) FALSE
#endif
#endif #endif
/** /**
@@ -326,7 +328,7 @@ struct RCptr {
*/ */
#ifndef MRB_NO_FLOAT #ifndef MRB_NO_FLOAT
MRB_INLINE mrb_value MRB_INLINE mrb_value
mrb_float_value(struct mrb_state *mrb, mrb_float f) mrb_float_value(mrb_state *mrb, mrb_float f)
{ {
mrb_value v; mrb_value v;
(void) mrb; (void) mrb;
@@ -336,7 +338,7 @@ mrb_float_value(struct mrb_state *mrb, mrb_float f)
#endif #endif
MRB_INLINE mrb_value MRB_INLINE mrb_value
mrb_cptr_value(struct mrb_state *mrb, void *p) mrb_cptr_value(mrb_state *mrb, void *p)
{ {
mrb_value v; mrb_value v;
(void) mrb; (void) mrb;
@@ -348,7 +350,7 @@ mrb_cptr_value(struct mrb_state *mrb, void *p)
* Returns an integer in Ruby. * Returns an integer in Ruby.
*/ */
MRB_INLINE mrb_value MRB_INLINE mrb_value
mrb_int_value(struct mrb_state *mrb, mrb_int i) mrb_int_value(mrb_state *mrb, mrb_int i)
{ {
mrb_value v; mrb_value v;
SET_INT_VALUE(mrb, v, i); SET_INT_VALUE(mrb, v, i);
+1 -1
View File
@@ -80,7 +80,7 @@ MRB_BEGIN_DECL
/* /*
* Release year. * Release year.
*/ */
#define MRUBY_RELEASE_YEAR 2025 #define MRUBY_RELEASE_YEAR 2026
/* /*
* Release month. * Release month.
+1 -2
View File
@@ -41,7 +41,7 @@ module MRuby
allocf.c allocf.c
readnum.c readnum.c
readint.c readint.c
readfloat.c fp_uscale.c
state.c state.c
symbol.c symbol.c
class.c class.c
@@ -66,7 +66,6 @@ module MRuby
cdump.c cdump.c
codedump.c codedump.c
print.c print.c
fmt_fp.c
debug.c debug.c
etc.c etc.c
version.c version.c
+26 -1
View File
@@ -81,7 +81,7 @@ module MRuby
include LoadGems include LoadGems
attr_accessor :name, :bins, :exts, :file_separator, :build_dir, :gem_clone_dir, :defines, :libdir_name attr_accessor :name, :bins, :exts, :file_separator, :build_dir, :gem_clone_dir, :defines, :libdir_name
attr_reader :products, :libmruby_core_objs, :libmruby_objs, :gems, :toolchains, :presym, :mrbc_build, :gem_dir_to_repo_url attr_reader :products, :libmruby_core_objs, :libmruby_objs, :gems, :toolchains, :presym, :mrbc_build, :gem_dir_to_repo_url
attr_reader :install_excludes attr_reader :install_excludes, :port_names
alias libmruby libmruby_objs alias libmruby libmruby_objs
@@ -138,6 +138,7 @@ module MRuby
@mrbcfile_external = false @mrbcfile_external = false
@internal = internal @internal = internal
@toolchains = [] @toolchains = []
@port_names = nil
@gem_dir_to_repo_url = {} @gem_dir_to_repo_url = {}
# Add lambda instead of string because libdir_name or lib may be changed by user configuration # Add lambda instead of string because libdir_name or lib may be changed by user configuration
@@ -183,6 +184,30 @@ module MRuby
@enable_debug = true @enable_debug = true
end end
# Set target port names for this build.
# Each gem compiles the first matching ports/<name>/ directory;
# later names in the list act as fallbacks for gems that don't
# ship a port for the earlier names.
# conf.ports :esp32
# conf.ports :rp2040, :posix # use rp2040 if available, else posix
def ports(*names)
@port_names = names.map { |n| n.to_s }
end
# Returns the effective port names for this build.
# If not explicitly set, auto-detects :posix or :win for host builds.
def effective_ports
return @port_names if @port_names
if kind_of?(MRuby::CrossBuild)
[]
elsif ENV['OS'] == 'Windows_NT' ||
('A'..'Z').any? { |v| Dir.exist?("#{v}:") }
['win']
else
['posix']
end
end
def disable_lock def disable_lock
@enable_lock = false @enable_lock = false
end end
+10 -5
View File
@@ -343,16 +343,21 @@ module MRuby
opt = @compile_options % {funcname: funcname} opt = @compile_options % {funcname: funcname}
opt << " -S" if cdump opt << " -S" if cdump
opt << " -s" if static opt << " -s" if static
# Have mrbc write to a private tempfile (-o) instead of stdout (-o-)
# to avoid pipe-inheritance races with parallel rake on Windows MinGW,
# where unrelated _pp build-progress lines from sibling workers can
# leak into the captured stdout and corrupt the generated C file.
tmpout = "#{out.path}.#{funcname}.mrbcout"
opt = opt.sub(/\s-o-(?=\s|\z)/, %Q[ -o "#{filename tmpout}"])
cmd = %["#{filename @command}" #{opt} #{filename(infiles).map{|f| %["#{f}"]}.join(' ')}] cmd = %["#{filename @command}" #{opt} #{filename(infiles).map{|f| %["#{f}"]}.join(' ')}]
puts cmd if Rake.verbose puts cmd if Rake.verbose
IO.popen(cmd, 'r') do |io| unless system(cmd)
out.puts io.read rm_f tmpout
end
# if mrbc execution fail, drop the file
unless $?.success?
rm_f out.path rm_f out.path
fail "Command failed with status (#{$?.exitstatus}): [#{cmd[0,42]}...]" fail "Command failed with status (#{$?.exitstatus}): [#{cmd[0,42]}...]"
end end
out.write File.binread(tmpout)
rm_f tmpout
end end
end end
+56 -1
View File
@@ -25,6 +25,7 @@ module MRuby
alias :author= :authors= alias :author= :authors=
attr_accessor :rbfiles, :objs attr_accessor :rbfiles, :objs
attr_reader :port_objs
attr_writer :test_objs, :test_rbfiles attr_writer :test_objs, :test_rbfiles
attr_accessor :test_args, :test_preload attr_accessor :test_args, :test_preload
@@ -43,6 +44,7 @@ module MRuby
def initialize(name, &block) def initialize(name, &block)
@name = name @name = name
@initializer = block @initializer = block
@post_user_config = nil
@version = "0.0.0" @version = "0.0.0"
@dependencies = [] @dependencies = []
@conflicts = [] @conflicts = []
@@ -59,6 +61,22 @@ module MRuby
@rbfiles = Dir.glob("#{@dir}/mrblib/**/*.rb").sort @rbfiles = Dir.glob("#{@dir}/mrblib/**/*.rb").sort
@objs = srcs_to_objs("src") @objs = srcs_to_objs("src")
# Add platform-specific sources from the first matching
# ports/<name>/ directory. effective_ports is a fallback
# chain: later names act as defaults for gems that don't ship
# a port for the earlier names. These objs are tracked
# separately so List#resolve_external_hal! can drop them when
# an external HAL provider (gem named hal-<short>-*) is loaded.
@port_objs = []
build.effective_ports.each do |port|
port_dir = "#{@dir}/ports/#{port}"
if File.directory?(port_dir)
@port_objs = srcs_to_objs("ports/#{port}")
@objs += @port_objs
break
end
end
@test_preload = nil # 'test/assert.rb' @test_preload = nil # 'test/assert.rb'
@test_args = {} @test_args = {}
@skip_test = false @skip_test = false
@@ -85,6 +103,7 @@ module MRuby
build.libmruby_objs << @objs build.libmruby_objs << @objs
instance_eval(&@build_config_initializer) if @build_config_initializer instance_eval(&@build_config_initializer) if @build_config_initializer
instance_eval(&@post_user_config) if @post_user_config
repo_url = build.gem_dir_to_repo_url[dir] repo_url = build.gem_dir_to_repo_url[dir]
build.locks[repo_url]['version'] = version if repo_url build.locks[repo_url]['version'] = version if repo_url
@@ -109,7 +128,8 @@ module MRuby
if build.kind_of?(MRuby::CrossBuild) if build.kind_of?(MRuby::CrossBuild)
return %w(x86_64-w64-mingw32 i686-w64-mingw32).include?(build.host_target) return %w(x86_64-w64-mingw32 i686-w64-mingw32).include?(build.host_target)
elsif build.kind_of?(MRuby::Build) elsif build.kind_of?(MRuby::Build)
return ('A'..'Z').to_a.any? { |vol| Dir.exist?("#{vol}:") } return ('A'..'Z').to_a.any? { |vol| Dir.exist?("#{vol}:") } ||
('a'..'z').to_a.any? { |vol| Dir.exist?("/#{vol}/") }
end end
return false return false
end end
@@ -193,6 +213,19 @@ module MRuby
end end
end end
# Register a block that runs after the user's `build.gem` block has
# been processed. Intended for gem authors to fill in defaults that
# depend on user-supplied configuration (e.g. auto-detect a library
# only if the user didn't specify which to use).
#
# Initialization order:
# 1. block in `MRuby::Gem::Specification.new` (gem author)
# 2. block in `build.gem` (user's build_config)
# 3. block in `post_user_config` (gem author, this hook)
def post_user_config(&block)
@post_user_config = block
end
def build_settings(&blk) def build_settings(&blk)
@build_settings = blk @build_settings = blk
end end
@@ -429,6 +462,28 @@ module MRuby
self.each(&:setup) self.each(&:setup)
gemset = self.setup_dependencies(build).keys.sort gemset = self.setup_dependencies(build).keys.sort
end until gemset == gemset_prev end until gemset == gemset_prev
resolve_external_hal!
end
# A gem named `hal-<short>-<conf>` is treated as the external
# HAL provider for the gem whose name's last `-`-separated
# segment is <short> (e.g., hal-task-glib provides the HAL for
# mruby-task). The target gem's ports/* sources are dropped
# from its object list -- the matching gem supplies the
# implementation. Two or more matches is a build error.
def resolve_external_hal!
each do |target|
next if target.port_objs.nil? || target.port_objs.empty?
short = target.name.split('-').last
pattern = /\Ahal-#{Regexp.escape(short)}-.+\z/
overriders = select { |g| g != target && g.name =~ pattern }
next if overriders.empty?
if overriders.size > 1
fail "Multiple HAL providers for '#{target.name}': " +
overriders.map(&:name).join(", ")
end
target.objs.reject! { |o| target.port_objs.include?(o) }
end
end end
def setup_build def setup_build
-7
View File
@@ -1,7 +0,0 @@
MRuby::Gem::Specification.new('hal-posix-dir') do |spec|
spec.license = 'MIT'
spec.authors = 'mruby developers'
spec.summary = 'POSIX HAL for mruby-dir (Linux, macOS, BSD, Unix)'
spec.add_dependency 'mruby-dir', core: 'mruby-dir'
end
-8
View File
@@ -1,8 +0,0 @@
MRuby::Gem::Specification.new('hal-posix-io') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
spec.summary = 'POSIX HAL for mruby-io (Linux, macOS, BSD, Unix)'
# HAL gem depends on feature gem - brings in mruby-io automatically
spec.add_dependency 'mruby-io', core: 'mruby-io'
end
-8
View File
@@ -1,8 +0,0 @@
MRuby::Gem::Specification.new('hal-posix-socket') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
spec.summary = 'POSIX HAL for mruby-socket (Linux, macOS, BSD, Unix)'
# HAL gem depends on feature gem - brings in mruby-socket automatically
spec.add_dependency 'mruby-socket', core: 'mruby-socket'
end
-102
View File
@@ -1,102 +0,0 @@
# hal-posix-task
POSIX Hardware Abstraction Layer (HAL) implementation for mruby-task.
## Description
Provides timer and interrupt support for the mruby-task cooperative scheduler on POSIX-compliant platforms. Uses `SIGALRM` and `setitimer()` for periodic timer ticks, and `sigprocmask()` for interrupt protection.
## Supported Platforms
- Linux
- macOS
- BSD (FreeBSD, OpenBSD, NetBSD)
- Other POSIX-compliant Unix systems
## Requirements
- POSIX-compliant operating system
- Signal support (`SIGALRM`, `sigaction`, `sigprocmask`)
- Timer support (`setitimer`, `ITIMER_REAL`)
## Usage
### Explicit HAL Selection (Recommended)
```ruby
MRuby::Build.new do |conf|
# ... other configuration ...
# Specify POSIX HAL - automatically brings in mruby-task
conf.gem core: 'hal-posix-task'
end
```
### Auto-detection (Development)
```ruby
MRuby::Build.new do |conf|
# ... other configuration ...
# Auto-detects and selects hal-posix-task on POSIX platforms
conf.gem core: 'mruby-task'
end
```
## Implementation Details
### Timer Mechanism
- Uses `setitimer(ITIMER_REAL, ...)` to generate periodic `SIGALRM` signals
- Timer interval configured by `MRB_TICK_UNIT` (default: 4ms)
- Signal handler calls `mrb_tick()` for all registered VM instances
### Interrupt Protection
- Critical sections protected using `sigprocmask()` to block `SIGALRM`
- Prevents race conditions during task queue modifications
- Supports nested critical sections through signal masking
### Multi-VM Support
- Supports up to `MRB_TASK_MAX_VMS` concurrent mruby VM instances (default: 8)
- Single shared timer ticks all registered VMs
- Per-VM task counters optimize timer usage (timer disabled when idle)
### Timer Optimization
The implementation dynamically enables/disables the timer based on task state:
- **Timer enabled** when: Multiple ready tasks OR any waiting tasks exist
- **Timer disabled** when: Single task or all tasks dormant/suspended
- Reduces CPU usage and power consumption when scheduler is idle
## Configuration
Override these macros in your build config if needed:
```ruby
conf.gem core: 'hal-posix-task' do |spec|
# Custom tick interval (10ms instead of default 4ms)
spec.build.defines << 'MRB_TICK_UNIT=10'
# Custom timeslice (5 ticks instead of default 3)
spec.build.defines << 'MRB_TIMESLICE_TICK_COUNT=5'
# More concurrent VMs (16 instead of default 8)
spec.build.defines << 'MRB_TASK_MAX_VMS=16'
end
```
## Known Limitations
- `SIGALRM` conflicts with other code using the same signal
- Timer resolution limited by platform (typically 1-10ms)
- Signal delivery may be delayed under heavy system load
- Not suitable for hard real-time requirements
## See Also
- `mruby-task` - Core task scheduler
- `hal-win-task` - Windows HAL implementation
- Task scheduler documentation: `mrbgems/mruby-task/README.md`
-8
View File
@@ -1,8 +0,0 @@
MRuby::Gem::Specification.new('hal-posix-task') do |spec|
spec.license = 'MIT'
spec.authors = 'mruby developers'
spec.summary = 'POSIX HAL for mruby-task (Linux, macOS, BSD, Unix)'
# HAL gem depends on feature gem - brings in mruby-task automatically
spec.add_dependency 'mruby-task', core: 'mruby-task'
end
-7
View File
@@ -1,7 +0,0 @@
MRuby::Gem::Specification.new('hal-win-dir') do |spec|
spec.license = 'MIT and MIT-like license'
spec.authors = ['mruby developers', 'Kevlin Henney']
spec.summary = 'Windows HAL for mruby-dir'
spec.add_dependency 'mruby-dir', core: 'mruby-dir'
end
-11
View File
@@ -1,11 +0,0 @@
MRuby::Gem::Specification.new('hal-win-io') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
spec.summary = 'Windows HAL for mruby-io (Windows, MinGW)'
# HAL gem depends on feature gem - brings in mruby-io automatically
spec.add_dependency 'mruby-io', core: 'mruby-io'
# Link Windows socket library
spec.linker.libraries << "ws2_32"
end
-12
View File
@@ -1,12 +0,0 @@
MRuby::Gem::Specification.new('hal-win-socket') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
spec.summary = 'Windows HAL for mruby-socket (Windows, MinGW)'
# HAL gem depends on feature gem - brings in mruby-socket automatically
spec.add_dependency 'mruby-socket', core: 'mruby-socket'
# Link Windows socket libraries
spec.linker.libraries << "wsock32"
spec.linker.libraries << "ws2_32"
end
-176
View File
@@ -1,176 +0,0 @@
/*
** socket_hal.c - Windows HAL implementation for mruby-socket
**
** See Copyright Notice in mruby.h
**
** Windows implementation for socket operations using Winsock APIs.
** Supported platforms: Windows, MinGW
*/
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 // need Windows XP or later
#endif
#include <mruby.h>
#include <mruby/string.h>
#include <mruby/class.h>
#include <mruby/error.h>
#include "socket_hal.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <errno.h>
#include <string.h>
/*
* Socket HAL Initialization/Finalization
*/
void
mrb_hal_socket_init(mrb_state *mrb)
{
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != NO_ERROR) {
mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(RuntimeError)), "WSAStartup failed");
}
}
void
mrb_hal_socket_final(mrb_state *mrb)
{
(void)mrb;
WSACleanup();
}
/*
* Socket Control Operations
*/
int
mrb_hal_socket_set_nonblock(mrb_state *mrb, int fd, int nonblock)
{
(void)mrb;
u_long mode = nonblock ? 1 : 0;
int result = ioctlsocket(fd, FIONBIO, &mode);
if (result != NO_ERROR) {
return -1;
}
return 0;
}
/*
* Address Conversion Functions
*/
const char*
mrb_hal_socket_inet_ntop(int af, const void *src, char *dst, size_t size)
{
if (af == AF_INET) {
struct sockaddr_in in = {0};
in.sin_family = AF_INET;
memcpy(&in.sin_addr, src, sizeof(struct in_addr));
if (getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in),
dst, (DWORD)size, NULL, 0, NI_NUMERICHOST) == 0) {
return dst;
}
return NULL;
}
else if (af == AF_INET6) {
struct sockaddr_in6 in = {0};
in.sin6_family = AF_INET6;
memcpy(&in.sin6_addr, src, sizeof(struct in6_addr));
if (getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6),
dst, (DWORD)size, NULL, 0, NI_NUMERICHOST) == 0) {
return dst;
}
return NULL;
}
return NULL;
}
int
mrb_hal_socket_inet_pton(int af, const char *src, void *dst)
{
struct addrinfo hints = {0};
hints.ai_family = af;
hints.ai_flags = AI_NUMERICHOST;
struct addrinfo *res;
if (getaddrinfo(src, NULL, &hints, &res) != 0) {
return 0; /* Invalid address */
}
if (res == NULL) {
return 0;
}
if (af == AF_INET && res->ai_family == AF_INET) {
memcpy(dst, &((struct sockaddr_in*)res->ai_addr)->sin_addr, sizeof(struct in_addr));
freeaddrinfo(res);
return 1;
}
else if (af == AF_INET6 && res->ai_family == AF_INET6) {
memcpy(dst, &((struct sockaddr_in6*)res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
freeaddrinfo(res);
return 1;
}
freeaddrinfo(res);
return 0;
}
/*
* Platform-Specific Socket Features
*/
mrb_value
mrb_hal_socket_sockaddr_un(mrb_state *mrb, const char *path, size_t pathlen)
{
(void)path;
(void)pathlen;
mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(NotImplementedError)),
"sockaddr_un unsupported on Windows");
return mrb_nil_value();
}
int
mrb_hal_socket_socketpair(mrb_state *mrb, int domain, int type, int protocol, int sv[2])
{
(void)mrb;
(void)domain;
(void)type;
(void)protocol;
(void)sv;
/* socketpair is not supported on Windows */
errno = ENOSYS;
return -1;
}
mrb_value
mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen)
{
(void)sockaddr;
(void)socklen;
mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(NotImplementedError)),
"unix_path unsupported on Windows");
return mrb_nil_value();
}
/*
* Gem initialization
*/
void
mrb_hal_win_socket_gem_init(mrb_state *mrb)
{
(void)mrb;
/* HAL interface functions are called by mruby-socket gem */
}
void
mrb_hal_win_socket_gem_final(mrb_state *mrb)
{
(void)mrb;
/* Cleanup handled by mrb_hal_socket_final called from mruby-socket */
}
-109
View File
@@ -1,109 +0,0 @@
# hal-win-task
Windows Hardware Abstraction Layer (HAL) implementation for mruby-task.
## Description
Provides timer and interrupt support for the mruby-task cooperative scheduler on Windows platforms. Uses multimedia timer (`timeSetEvent`/`timeKillEvent`) for periodic timer ticks, and `CRITICAL_SECTION` for interrupt protection.
## Supported Platforms
- Windows 7 and later
- Windows Server 2008 R2 and later
- All versions with multimedia timer support
## Requirements
- Windows operating system
- Multimedia timer API (`winmm.lib`)
- Visual C++, MinGW, or compatible compiler
## Usage
### Explicit HAL Selection (Recommended)
```ruby
MRuby::Build.new do |conf|
# ... other configuration ...
# Specify Windows HAL - automatically brings in mruby-task
conf.gem core: 'hal-win-task'
end
```
### Auto-detection (Development)
```ruby
MRuby::Build.new do |conf|
# ... other configuration ...
# Auto-detects and selects hal-win-task on Windows platforms
conf.gem core: 'mruby-task'
end
```
## Implementation Details
### Timer Mechanism
- Uses Windows multimedia timer (`timeSetEvent`) for periodic callbacks
- Timer interval configured by `MRB_TICK_UNIT` (default: 4ms)
- `TIME_KILL_SYNCHRONOUS` flag ensures clean timer shutdown
- Requests 1ms timer resolution via `timeBeginPeriod(1)`
### Interrupt Protection
- Critical sections protected using `CRITICAL_SECTION` objects
- Prevents race conditions during task queue modifications
- `EnterCriticalSection`/`LeaveCriticalSection` used for mutual exclusion
- Supports nested critical sections (automatic lock counting)
### Multi-VM Support
- Supports up to `MRB_TASK_MAX_VMS` concurrent mruby VM instances (default: 8)
- Single shared timer ticks all registered VMs
- Per-VM task counters optimize timer usage (timer disabled when idle)
- Interlocked operations (`InterlockedIncrement`/`InterlockedDecrement`) for thread safety
### Timer Optimization
The implementation dynamically enables/disables the timer based on task state:
- **Timer enabled** when: Multiple ready tasks OR any waiting tasks exist
- **Timer disabled** when: Single task or all tasks dormant/suspended
- Reduces CPU usage and power consumption when scheduler is idle
## Configuration
Override these macros in your build config if needed:
```ruby
conf.gem core: 'hal-win-task' do |spec|
# Custom tick interval (10ms instead of default 4ms)
spec.build.defines << 'MRB_TICK_UNIT=10'
# Custom timeslice (5 ticks instead of default 3)
spec.build.defines << 'MRB_TIMESLICE_TICK_COUNT=5'
# More concurrent VMs (16 instead of default 8)
spec.build.defines << 'MRB_TASK_MAX_VMS=16'
end
```
## Known Limitations
- Timer resolution typically limited to 1-2ms even with `timeBeginPeriod(1)`
- Multimedia timers consume system resources (kernel timer objects)
- Timer callbacks execute in separate thread context (handled internally)
- Not suitable for hard real-time requirements
- May interfere with other multimedia applications requesting different timer resolutions
## See Also
- `mruby-task` - Core task scheduler
- `hal-posix-task` - POSIX/Unix HAL implementation
- Task scheduler documentation: `mrbgems/mruby-task/README.md`
## Build Notes
The `winmm` library is automatically linked by the gem specification. No additional linker configuration is needed.
-11
View File
@@ -1,11 +0,0 @@
MRuby::Gem::Specification.new('hal-win-task') do |spec|
spec.license = 'MIT'
spec.authors = 'mruby developers'
spec.summary = 'Windows HAL for mruby-task'
# HAL gem depends on feature gem - brings in mruby-task automatically
spec.add_dependency 'mruby-task', core: 'mruby-task'
# Windows multimedia timer library
spec.linker.libraries << 'winmm'
end
+74
View File
@@ -0,0 +1,74 @@
# hw-adc - ADC peripheral interface for mruby
This gem provides the `ADC` class for reading analog input from
mruby. It is designed for embedded platforms such as ESP32 and
RP2040.
## Architecture
Platform-specific HAL implementations are in `ports/` directories:
- `ports/esp32/` - ESP32 using ESP-IDF ADC oneshot driver
- `ports/rp2040/` - RP2040 using Pico SDK ADC hardware
## Build Configuration
```ruby
MRuby::CrossBuild.new('esp32') do |conf|
conf.ports :esp32
conf.gem core: 'hw-adc'
end
```
## Ruby API
### ADC.new
```ruby
adc = ADC.new(pin)
```
- `pin` - ADC-capable GPIO pin number (Integer)
- Raises `ArgumentError` if the pin is not valid for ADC
### ADC#read / ADC#read_voltage
Read the analog value as voltage (Float).
```ruby
voltage = adc.read # => 1.65
voltage = adc.read_voltage # same
```
### ADC#read_raw
Read the raw ADC value (Integer, typically 0-4095 for 12-bit).
```ruby
raw = adc.read_raw # => 2048
```
### ADC#input
Returns the ADC input channel number assigned during initialization.
## HAL Interface
To add support for a new platform, create a `ports/<name>/`
directory and implement the following C functions declared in
`<mruby/adc.h>`:
```c
int mrb_adc_init(uint8_t pin);
uint32_t mrb_adc_read_raw(uint8_t input);
float mrb_adc_read_voltage(uint8_t input);
```
- `mrb_adc_init` returns the input channel number (>= 0) on
success, negative on error
- `input` parameter for read functions is the value returned by
`mrb_adc_init`
## License
MIT
+19
View File
@@ -0,0 +1,19 @@
#ifndef MRUBY_ADC_H
#define MRUBY_ADC_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* HAL functions - implemented in ports/<platform>/adc.c */
int mrb_adc_init(uint8_t pin);
uint32_t mrb_adc_read_raw(uint8_t input);
float mrb_adc_read_voltage(uint8_t input);
#ifdef __cplusplus
}
#endif
#endif /* MRUBY_ADC_H */
+5
View File
@@ -0,0 +1,5 @@
MRuby::Gem::Specification.new('hw-adc') do |spec|
spec.license = 'MIT'
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
spec.summary = 'ADC peripheral interface'
end
+7
View File
@@ -0,0 +1,7 @@
class ADC
def initialize(pin)
@input = __init(pin)
end
attr_reader :input
end
+147
View File
@@ -0,0 +1,147 @@
#include <stdbool.h>
#include "esp_adc/adc_oneshot.h"
#include <mruby/adc.h>
#define VOLTAGE_MAX 3.3f
#define RESOLUTION 4095
#define UNIT_NUM 2
static adc_oneshot_unit_handle_t adc_handles[UNIT_NUM];
static bool adc_initialized;
static adc_unit_t
pin_to_unit(uint8_t pin)
{
switch (pin) {
#if CONFIG_IDF_TARGET_ESP32C3
case 0: case 1: case 2: case 3: case 4:
return ADC_UNIT_1;
case 5:
return ADC_UNIT_2;
#elif CONFIG_IDF_TARGET_ESP32
case 32: case 33: case 34: case 35: case 36: case 39:
return ADC_UNIT_1;
case 0: case 2: case 4: case 12: case 13: case 14: case 15:
case 25: case 26: case 27:
return ADC_UNIT_2;
#elif (CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2)
case 1: case 2: case 3: case 4: case 5:
case 6: case 7: case 8: case 9: case 10:
return ADC_UNIT_1;
case 11: case 12: case 13: case 14: case 15:
case 16: case 17: case 18: case 19: case 20:
return ADC_UNIT_2;
#endif
}
return -1;
}
static adc_channel_t
pin_to_channel(uint8_t pin)
{
switch (pin) {
#if CONFIG_IDF_TARGET_ESP32C3
case 0: return ADC_CHANNEL_0;
case 1: return ADC_CHANNEL_1;
case 2: return ADC_CHANNEL_2;
case 3: return ADC_CHANNEL_3;
case 4: return ADC_CHANNEL_4;
case 5: return ADC_CHANNEL_0;
#elif CONFIG_IDF_TARGET_ESP32
case 36: return ADC_CHANNEL_0;
case 39: return ADC_CHANNEL_3;
case 32: return ADC_CHANNEL_4;
case 33: return ADC_CHANNEL_5;
case 34: return ADC_CHANNEL_6;
case 35: return ADC_CHANNEL_7;
case 4: return ADC_CHANNEL_0;
case 0: return ADC_CHANNEL_1;
case 2: return ADC_CHANNEL_2;
case 15: return ADC_CHANNEL_3;
case 13: return ADC_CHANNEL_4;
case 12: return ADC_CHANNEL_5;
case 14: return ADC_CHANNEL_6;
case 27: return ADC_CHANNEL_7;
case 25: return ADC_CHANNEL_8;
case 26: return ADC_CHANNEL_9;
#elif (CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2)
case 1: return ADC_CHANNEL_0;
case 2: return ADC_CHANNEL_1;
case 3: return ADC_CHANNEL_2;
case 4: return ADC_CHANNEL_3;
case 5: return ADC_CHANNEL_4;
case 6: return ADC_CHANNEL_5;
case 7: return ADC_CHANNEL_6;
case 8: return ADC_CHANNEL_7;
case 9: return ADC_CHANNEL_8;
case 10: return ADC_CHANNEL_9;
case 11: return ADC_CHANNEL_0;
case 12: return ADC_CHANNEL_1;
case 13: return ADC_CHANNEL_2;
case 14: return ADC_CHANNEL_3;
case 15: return ADC_CHANNEL_4;
case 16: return ADC_CHANNEL_5;
case 17: return ADC_CHANNEL_6;
case 18: return ADC_CHANNEL_7;
case 19: return ADC_CHANNEL_8;
case 20: return ADC_CHANNEL_9;
#endif
}
return -1;
}
static int
init_units(void)
{
adc_unit_t units[] = { ADC_UNIT_1, ADC_UNIT_2 };
for (int i = 0; i < UNIT_NUM; i++) {
adc_oneshot_unit_init_cfg_t cfg = {
.unit_id = units[i],
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
if (adc_oneshot_new_unit(&cfg, &adc_handles[i]) != ESP_OK)
return -1;
}
return 0;
}
int
mrb_adc_init(uint8_t pin)
{
if (!adc_initialized) {
if (init_units() != 0) return -1;
adc_initialized = true;
}
int ch = pin_to_channel(pin);
if (ch < 0) return -1;
adc_unit_t unit = pin_to_unit(pin);
if (unit < 0 || unit >= UNIT_NUM) return -1;
adc_oneshot_chan_cfg_t cfg = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
if (adc_oneshot_config_channel(adc_handles[unit], ch, &cfg) != ESP_OK)
return -1;
return (int)pin;
}
uint32_t
mrb_adc_read_raw(uint8_t input)
{
adc_unit_t unit = pin_to_unit(input);
int ch = pin_to_channel(input);
int raw = 0;
if (unit >= 0 && unit < UNIT_NUM)
adc_oneshot_read(adc_handles[unit], ch, &raw);
return (uint32_t)raw;
}
float
mrb_adc_read_voltage(uint8_t input)
{
return (float)mrb_adc_read_raw(input) * VOLTAGE_MAX / RESOLUTION;
}
+43
View File
@@ -0,0 +1,43 @@
#include <stdbool.h>
#include "hardware/adc.h"
#include <mruby/adc.h>
#define VOLTAGE_MAX 3.3f
#define RESOLUTION 4095
#define TEMP_INPUT 4
static bool adc_initialized;
int
mrb_adc_init(uint8_t pin)
{
if (!adc_initialized) {
adc_init();
adc_initialized = true;
}
uint input;
switch (pin) {
case 26: input = 0; break;
case 27: input = 1; break;
case 28: input = 2; break;
case 29: input = 3; break;
default: return -1;
}
adc_gpio_init(pin);
return (int)input;
}
uint32_t
mrb_adc_read_raw(uint8_t input)
{
adc_select_input(input);
return (uint32_t)adc_read();
}
float
mrb_adc_read_voltage(uint8_t input)
{
adc_select_input(input);
return (float)adc_read() * VOLTAGE_MAX / RESOLUTION;
}
+48
View File
@@ -0,0 +1,48 @@
#include <mruby.h>
#include <mruby/presym.h>
#include <mruby/variable.h>
#include <mruby/adc.h>
/* ADC#__init(pin) */
static mrb_value
mrb_adc_m_init(mrb_state *mrb, mrb_value self)
{
mrb_int pin;
mrb_get_args(mrb, "i", &pin);
int input = mrb_adc_init((uint8_t)pin);
if (input < 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid ADC pin");
}
return mrb_fixnum_value(input);
}
/* ADC#read_raw */
static mrb_value
mrb_adc_m_read_raw(mrb_state *mrb, mrb_value self)
{
mrb_int input = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(input)));
return mrb_fixnum_value(mrb_adc_read_raw((uint8_t)input));
}
/* ADC#read_voltage (also aliased as read) */
static mrb_value
mrb_adc_m_read_voltage(mrb_state *mrb, mrb_value self)
{
mrb_int input = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(input)));
return mrb_float_value(mrb, (mrb_float)mrb_adc_read_voltage((uint8_t)input));
}
void
mrb_hw_adc_gem_init(mrb_state *mrb)
{
struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(ADC), mrb->object_class);
mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_adc_m_init, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, cls, MRB_SYM(read_raw), mrb_adc_m_read_raw, MRB_ARGS_NONE());
mrb_define_method_id(mrb, cls, MRB_SYM(read_voltage), mrb_adc_m_read_voltage, MRB_ARGS_NONE());
mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_adc_m_read_voltage, MRB_ARGS_NONE());
}
void
mrb_hw_adc_gem_final(mrb_state *mrb)
{
}
+144
View File
@@ -0,0 +1,144 @@
# hw-gpio - GPIO peripheral interface for mruby
This gem provides the `GPIO` class for controlling general-purpose
input/output pins from mruby.
It is designed for embedded platforms such as ESP32 and RP2040.
## Architecture
Platform-specific HAL implementations are in `ports/` directories:
- `ports/esp32/` - ESP32 using ESP-IDF GPIO driver
- `ports/rp2040/` - RP2040 using Pico SDK
The build system automatically compiles matching port sources based
on `conf.ports` setting.
## Build Configuration
```ruby
# For ESP32
MRuby::CrossBuild.new('esp32') do |conf|
conf.ports :esp32
conf.gem core: 'hw-gpio'
end
# For RP2040
MRuby::CrossBuild.new('rp2040') do |conf|
conf.ports :rp2040
conf.gem core: 'hw-gpio'
end
```
## Ruby API
### Constants (direction/mode flags)
These flags can be combined with bitwise OR.
| Flag | Value | Description |
| ------------------ | ------ | -------------------------- |
| `GPIO::IN` | `0x01` | Input mode |
| `GPIO::OUT` | `0x02` | Output mode |
| `GPIO::HIGH_Z` | `0x04` | High-impedance (tri-state) |
| `GPIO::PULL_UP` | `0x08` | Enable pull-up resistor |
| `GPIO::PULL_DOWN` | `0x10` | Enable pull-down resistor |
| `GPIO::OPEN_DRAIN` | `0x20` | Enable open-drain output |
### GPIO.new
```ruby
gpio = GPIO.new(pin, flags)
```
- `pin` - GPIO pin number (Integer)
- `flags` - direction and mode flags (combined with `|`)
- Exactly one of `IN`, `OUT`, or `HIGH_Z` must be specified
- `PULL_UP` and `PULL_DOWN` are mutually exclusive
- Raises `ArgumentError` on invalid flag combinations
```ruby
# Input with pull-up
button = GPIO.new(2, GPIO::IN | GPIO::PULL_UP)
# Output
led = GPIO.new(25, GPIO::OUT)
```
### Instance Methods
#### GPIO#read
Read the current pin value.
```ruby
val = gpio.read # => 0 or 1
```
#### GPIO#write
Set the pin output value.
```ruby
gpio.write(1) # set high
gpio.write(0) # set low
```
- Raises `ArgumentError` if value is not 0 or 1
#### GPIO#high? / GPIO#low?
```ruby
gpio.high? # => true if read != 0
gpio.low? # => true if read == 0
```
#### GPIO#pin
```ruby
gpio.pin # => pin number passed to new
```
#### GPIO#setmode
Reconfigure pin direction and mode flags after initialization.
```ruby
gpio.setmode(GPIO::OUT)
```
### Class Methods
These operate directly on pin numbers without creating an instance.
```ruby
GPIO.read_at(pin) # => 0 or 1
GPIO.write_at(pin, val) # set pin output (0 or 1)
GPIO.set_dir_at(pin, flags) # set direction (IN/OUT/HIGH_Z)
GPIO.pull_up_at(pin) # enable pull-up
GPIO.pull_down_at(pin) # enable pull-down
GPIO.open_drain_at(pin) # enable open-drain
```
## HAL Interface
To add support for a new platform, create a `ports/<name>/`
directory and implement the following C functions declared in
`<mruby/gpio.h>`:
```c
void mrb_gpio_init(uint8_t pin);
void mrb_gpio_set_dir(uint8_t pin, uint8_t flags);
void mrb_gpio_pull_up(uint8_t pin);
void mrb_gpio_pull_down(uint8_t pin);
void mrb_gpio_open_drain(uint8_t pin);
int mrb_gpio_read(uint8_t pin);
void mrb_gpio_write(uint8_t pin, uint8_t val);
```
The port sources are compiled automatically when the build
configuration includes a matching `conf.ports` tag.
## License
MIT
+31
View File
@@ -0,0 +1,31 @@
#ifndef MRUBY_GPIO_H
#define MRUBY_GPIO_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* direction/mode flags (bitmask) */
#define MRB_GPIO_IN 0x01
#define MRB_GPIO_OUT 0x02
#define MRB_GPIO_HIGH_Z 0x04
#define MRB_GPIO_PULL_UP 0x08
#define MRB_GPIO_PULL_DOWN 0x10
#define MRB_GPIO_OPEN_DRAIN 0x20
/* HAL functions - implemented by hw-<platform>-gpio gems */
void mrb_gpio_init(uint8_t pin);
void mrb_gpio_set_dir(uint8_t pin, uint8_t flags);
void mrb_gpio_pull_up(uint8_t pin);
void mrb_gpio_pull_down(uint8_t pin);
void mrb_gpio_open_drain(uint8_t pin);
int mrb_gpio_read(uint8_t pin);
void mrb_gpio_write(uint8_t pin, uint8_t val);
#ifdef __cplusplus
}
#endif
#endif /* MRUBY_GPIO_H */
+5
View File
@@ -0,0 +1,5 @@
MRuby::Gem::Specification.new('hw-gpio') do |spec|
spec.license = 'MIT'
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
spec.summary = 'GPIO peripheral interface'
end
+44
View File
@@ -0,0 +1,44 @@
class GPIO
IN = 0x01
OUT = 0x02
HIGH_Z = 0x04
PULL_UP = 0x08
PULL_DOWN = 0x10
OPEN_DRAIN = 0x20
attr_reader :pin
def initialize(pin, flags)
@pin = pin
__init(pin)
setmode(flags)
end
def setmode(flags)
dir = flags & (IN | OUT | HIGH_Z)
n = (flags & IN != 0 ? 1 : 0) + (flags & OUT != 0 ? 1 : 0) + (flags & HIGH_Z != 0 ? 1 : 0)
if n == 0
raise ArgumentError, "specify one of IN, OUT, or HIGH_Z"
elsif n > 1
raise ArgumentError, "IN, OUT, and HIGH_Z are exclusive"
end
GPIO.set_dir_at(@pin, dir)
pull = flags & (PULL_UP | PULL_DOWN)
if pull == (PULL_UP | PULL_DOWN)
raise ArgumentError, "PULL_UP and PULL_DOWN are exclusive"
end
GPIO.pull_up_at(@pin) if pull == PULL_UP
GPIO.pull_down_at(@pin) if pull == PULL_DOWN
GPIO.open_drain_at(@pin) if flags & OPEN_DRAIN != 0
nil
end
def high?
read != 0
end
def low?
read == 0
end
end
+50
View File
@@ -0,0 +1,50 @@
#include "driver/gpio.h"
#include <mruby/gpio.h>
void
mrb_gpio_init(uint8_t pin)
{
gpio_reset_pin(pin);
}
void
mrb_gpio_set_dir(uint8_t pin, uint8_t flags)
{
if (flags & MRB_GPIO_IN) {
gpio_set_direction(pin, GPIO_MODE_INPUT);
}
else if (flags & MRB_GPIO_OUT) {
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
}
/* HIGH_Z: not yet implemented */
}
void
mrb_gpio_pull_up(uint8_t pin)
{
gpio_pullup_en(pin);
}
void
mrb_gpio_pull_down(uint8_t pin)
{
gpio_pulldown_en(pin);
}
void
mrb_gpio_open_drain(uint8_t pin)
{
/* not yet implemented */
}
int
mrb_gpio_read(uint8_t pin)
{
return gpio_get_level(pin);
}
void
mrb_gpio_write(uint8_t pin, uint8_t val)
{
gpio_set_level(pin, val);
}
+51
View File
@@ -0,0 +1,51 @@
#include <stdbool.h>
#include "hardware/gpio.h"
#include <mruby/gpio.h>
void
mrb_gpio_init(uint8_t pin)
{
gpio_init(pin);
}
void
mrb_gpio_set_dir(uint8_t pin, uint8_t flags)
{
if (flags & MRB_GPIO_IN) {
gpio_set_dir(pin, false);
}
else if (flags & MRB_GPIO_OUT) {
gpio_set_dir(pin, true);
}
/* HIGH_Z: not yet implemented */
}
void
mrb_gpio_pull_up(uint8_t pin)
{
gpio_pull_up(pin);
}
void
mrb_gpio_pull_down(uint8_t pin)
{
gpio_pull_down(pin);
}
void
mrb_gpio_open_drain(uint8_t pin)
{
/* not yet implemented */
}
int
mrb_gpio_read(uint8_t pin)
{
return gpio_get(pin);
}
void
mrb_gpio_write(uint8_t pin, uint8_t val)
{
gpio_put(pin, val == 1);
}
+119
View File
@@ -0,0 +1,119 @@
#include <mruby.h>
#include <mruby/presym.h>
#include <mruby/variable.h>
#include <mruby/gpio.h>
static mrb_value
mrb_gpio_m_init(mrb_state *mrb, mrb_value self)
{
mrb_int pin;
mrb_get_args(mrb, "i", &pin);
mrb_gpio_init((uint8_t)pin);
return mrb_nil_value();
}
/* GPIO.set_dir_at(pin, flags) */
static mrb_value
mrb_gpio_s_set_dir_at(mrb_state *mrb, mrb_value klass)
{
mrb_int pin, flags;
mrb_get_args(mrb, "ii", &pin, &flags);
mrb_gpio_set_dir((uint8_t)pin, (uint8_t)flags);
return mrb_nil_value();
}
/* GPIO.pull_up_at(pin) */
static mrb_value
mrb_gpio_s_pull_up_at(mrb_state *mrb, mrb_value klass)
{
mrb_int pin;
mrb_get_args(mrb, "i", &pin);
mrb_gpio_pull_up((uint8_t)pin);
return mrb_nil_value();
}
/* GPIO.pull_down_at(pin) */
static mrb_value
mrb_gpio_s_pull_down_at(mrb_state *mrb, mrb_value klass)
{
mrb_int pin;
mrb_get_args(mrb, "i", &pin);
mrb_gpio_pull_down((uint8_t)pin);
return mrb_nil_value();
}
/* GPIO.open_drain_at(pin) */
static mrb_value
mrb_gpio_s_open_drain_at(mrb_state *mrb, mrb_value klass)
{
mrb_int pin;
mrb_get_args(mrb, "i", &pin);
mrb_gpio_open_drain((uint8_t)pin);
return mrb_nil_value();
}
/* GPIO.read_at(pin) */
static mrb_value
mrb_gpio_s_read_at(mrb_state *mrb, mrb_value klass)
{
mrb_int pin;
mrb_get_args(mrb, "i", &pin);
return mrb_fixnum_value(mrb_gpio_read((uint8_t)pin));
}
/* GPIO.write_at(pin, val) */
static mrb_value
mrb_gpio_s_write_at(mrb_state *mrb, mrb_value klass)
{
mrb_int pin, val;
mrb_get_args(mrb, "ii", &pin, &val);
if (val != 0 && val != 1) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be 0 or 1");
}
mrb_gpio_write((uint8_t)pin, (uint8_t)val);
return mrb_nil_value();
}
/* GPIO#read */
static mrb_value
mrb_gpio_m_read(mrb_state *mrb, mrb_value self)
{
mrb_int pin = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(pin)));
return mrb_fixnum_value(mrb_gpio_read((uint8_t)pin));
}
/* GPIO#write(val) */
static mrb_value
mrb_gpio_m_write(mrb_state *mrb, mrb_value self)
{
mrb_int val;
mrb_get_args(mrb, "i", &val);
if (val != 0 && val != 1) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be 0 or 1");
}
mrb_int pin = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(pin)));
mrb_gpio_write((uint8_t)pin, (uint8_t)val);
return mrb_nil_value();
}
void
mrb_hw_gpio_gem_init(mrb_state *mrb)
{
struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(GPIO), mrb->object_class);
mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_gpio_m_init, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_gpio_m_read, MRB_ARGS_NONE());
mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_gpio_m_write, MRB_ARGS_REQ(1));
mrb_define_class_method_id(mrb, cls, MRB_SYM(set_dir_at), mrb_gpio_s_set_dir_at, MRB_ARGS_REQ(2));
mrb_define_class_method_id(mrb, cls, MRB_SYM(pull_up_at), mrb_gpio_s_pull_up_at, MRB_ARGS_REQ(1));
mrb_define_class_method_id(mrb, cls, MRB_SYM(pull_down_at), mrb_gpio_s_pull_down_at, MRB_ARGS_REQ(1));
mrb_define_class_method_id(mrb, cls, MRB_SYM(open_drain_at), mrb_gpio_s_open_drain_at, MRB_ARGS_REQ(1));
mrb_define_class_method_id(mrb, cls, MRB_SYM(read_at), mrb_gpio_s_read_at, MRB_ARGS_REQ(1));
mrb_define_class_method_id(mrb, cls, MRB_SYM(write_at), mrb_gpio_s_write_at, MRB_ARGS_REQ(2));
}
void
mrb_hw_gpio_gem_final(mrb_state *mrb)
{
}
+162
View File
@@ -0,0 +1,162 @@
# hw-i2c - I2C peripheral interface for mruby
This gem provides the `I2C` class for communicating with I2C devices from mruby. It is designed for embedded platforms such as ESP32 and RP2040.
## Architecture
Platform-specific HAL implementations are in `ports/` directories:
- `ports/esp32/` - ESP32 using ESP-IDF I2C master driver
- `ports/rp2040/` - RP2040 using Pico SDK
The build system automatically compiles matching port sources based
on `conf.ports` setting.
## Build Configuration
```ruby
# For ESP32
MRuby::CrossBuild.new('esp32') do |conf|
conf.ports :esp32
conf.gem core: 'hw-i2c'
end
# For RP2040
MRuby::CrossBuild.new('rp2040') do |conf|
conf.ports :rp2040
conf.gem core: 'hw-i2c'
end
```
## Ruby API
### I2C.new
```ruby
i2c = I2C.new(
unit: :ESP32_I2C0, # I2C unit name (platform-specific, required)
frequency: 100_000, # bus frequency in Hz (default: 100kHz)
sda_pin: 21, # SDA GPIO pin number (default: -1 for platform default)
scl_pin: 22, # SCL GPIO pin number (default: -1 for platform default)
timeout: 500 # default timeout in ms (default: 500)
)
```
#### Unit Names
| Platform | Available Units |
| -------- | ------------------------------ |
| ESP32 | `:ESP32_I2C0`, `:ESP32_I2C1` |
| RP2040 | `:RP2040_I2C0`, `:RP2040_I2C1` |
On RP2040, if `sda_pin` or `scl_pin` is -1, the Pico SDK default pins are used.
### I2C#write
Write data to an I2C device.
```ruby
i2c.write(addr, *data, timeout: 500)
```
- `addr` - 7-bit I2C device address (Integer)
- `data` - one or more data arguments, each can be:
- **Integer** - a single byte (0-255)
- **Array of Integer** - multiple bytes
- **String** - raw bytes
- `timeout:` - optional timeout in ms (overrides instance default)
- Returns the number of bytes written (Integer)
- Raises `IOError` on failure
```ruby
# Write a single byte
i2c.write(0x3C, 0x00)
# Write multiple bytes
i2c.write(0x3C, 0x00, [0xAE, 0xD5, 0x80])
# Write a string
i2c.write(0x3C, "hello")
# Mix data types
i2c.write(0x3C, 0x40, [0x01, 0x02], "data")
```
### I2C#read
Read data from an I2C device. Optionally write data before reading (repeated START).
```ruby
i2c.read(addr, length, *write_data, timeout: 500)
```
- `addr` - 7-bit I2C device address (Integer)
- `length` - number of bytes to read (Integer, must be positive)
- `write_data` - optional data to write before reading (same format as `write`). When provided, the gem performs a write-then-read transaction using I2C repeated START condition. This is the standard way to read from a specific register.
- `timeout:` - optional timeout in ms (overrides instance default)
- Returns the data read (String)
- Raises `IOError` on failure, `ArgumentError` if length <= 0
```ruby
# Simple read (2 bytes from device)
data = i2c.read(0x50, 2)
# Register read: write register address 0x00, then read 2 bytes
data = i2c.read(0x50, 2, 0x00)
# Multi-byte register address
data = i2c.read(0x50, 4, [0x00, 0x10])
```
### I2C#scan
Scan the I2C bus for responsive devices.
```ruby
i2c.scan(timeout: 500)
```
- `timeout:` - optional timeout per probe in ms
- Returns an Array of 7-bit addresses (Integer) that responded
```ruby
found = i2c.scan
# => [0x3C, 0x50, 0x68]
```
## HAL Interface
To add support for a new platform, create a `ports/<name>/`
directory and implement the following C functions declared in
`<mruby/i2c.h>`:
```c
int mrb_i2c_unit_name_to_num(const char *name);
mrb_i2c_status mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl);
int mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len,
uint32_t timeout_us);
int mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len,
uint32_t timeout_us);
int mrb_i2c_write_read(int unit, uint8_t addr,
const uint8_t *src, size_t wlen,
uint8_t *dst, size_t rlen,
uint32_t timeout_us);
```
The port sources are compiled automatically when the build
configuration includes a matching `conf.ports` tag.
### Error Codes
```c
typedef enum {
MRB_I2C_OK = 0,
MRB_I2C_ERROR_UNIT = -1, /* invalid or uninitialized unit */
MRB_I2C_ERROR_TIMEOUT = -2, /* communication timeout */
MRB_I2C_ERROR_NACK = -3, /* device did not acknowledge */
} mrb_i2c_status;
```
## License
MIT
+31
View File
@@ -0,0 +1,31 @@
#ifndef MRUBY_I2C_H
#define MRUBY_I2C_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
MRB_I2C_OK = 0,
MRB_I2C_ERROR_UNIT = -1,
MRB_I2C_ERROR_TIMEOUT = -2,
MRB_I2C_ERROR_NACK = -3,
} mrb_i2c_status;
/* HAL functions - implemented by hw-<platform>-i2c gems */
mrb_i2c_status mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl);
int mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len, uint32_t timeout_us);
int mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len, uint32_t timeout_us);
int mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen,
uint8_t *dst, size_t rlen, uint32_t timeout_us);
int mrb_i2c_unit_name_to_num(const char *name);
#ifdef __cplusplus
}
#endif
#endif /* MRUBY_I2C_H */
+5
View File
@@ -0,0 +1,5 @@
MRuby::Gem::Specification.new('hw-i2c') do |spec|
spec.license = 'MIT'
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
spec.summary = 'I2C peripheral interface'
end
+21
View File
@@ -0,0 +1,21 @@
class I2C
DEFAULT_FREQUENCY = 100_000 # Hz
DEFAULT_TIMEOUT = 500 # ms
def initialize(unit:, frequency: DEFAULT_FREQUENCY, sda_pin: -1, scl_pin: -1, timeout: DEFAULT_TIMEOUT)
@timeout = timeout
@unit_num = __init(unit.to_s, frequency, sda_pin, scl_pin)
end
def scan(timeout: @timeout)
found = []
(0x08..0x77).each do |addr|
begin
read(addr, 1, timeout: timeout)
found << addr
rescue IOError
end
end
found
end
end
+117
View File
@@ -0,0 +1,117 @@
#include <string.h>
#include "driver/i2c_master.h"
#include <mruby/i2c.h>
typedef struct {
i2c_master_bus_handle_t bus;
uint32_t freq;
bool initialized;
} i2c_ctx;
/* ESP32 supports up to 2 I2C ports */
static i2c_ctx ctx[2];
static bool
valid_unit(int unit)
{
return unit >= 0 && unit <= 1 && ctx[unit].initialized;
}
static uint32_t
us_to_ms(uint32_t timeout_us)
{
uint32_t ms = (timeout_us + 999) / 1000;
return (ms < 10) ? 10 : ms;
}
static i2c_master_dev_handle_t
add_device(int unit, uint8_t addr)
{
i2c_device_config_t cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = addr,
.scl_speed_hz = ctx[unit].freq,
};
i2c_master_dev_handle_t dev;
if (i2c_master_bus_add_device(ctx[unit].bus, &cfg, &dev) != ESP_OK)
return NULL;
return dev;
}
int
mrb_i2c_unit_name_to_num(const char *name)
{
if (strcmp(name, "ESP32_I2C0") == 0) return 0;
if (strcmp(name, "ESP32_I2C1") == 0) return 1;
return MRB_I2C_ERROR_UNIT;
}
mrb_i2c_status
mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl)
{
if (unit < 0 || unit > 1) return MRB_I2C_ERROR_UNIT;
if (ctx[unit].initialized) {
i2c_del_master_bus(ctx[unit].bus);
ctx[unit].initialized = false;
}
i2c_master_bus_config_t cfg = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = unit,
.scl_io_num = scl,
.sda_io_num = sda,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
esp_err_t err = i2c_new_master_bus(&cfg, &ctx[unit].bus);
if (err != ESP_OK) return MRB_I2C_ERROR_UNIT;
ctx[unit].initialized = true;
ctx[unit].freq = freq;
return MRB_I2C_OK;
}
int
mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len,
uint32_t timeout_us)
{
if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT;
i2c_master_dev_handle_t dev = add_device(unit, addr);
if (!dev) return -1;
esp_err_t err = i2c_master_receive(dev, dst, len, us_to_ms(timeout_us));
i2c_master_bus_rm_device(dev);
return (err == ESP_OK) ? (int)len : -1;
}
int
mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len,
uint32_t timeout_us)
{
if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT;
i2c_master_dev_handle_t dev = add_device(unit, addr);
if (!dev) return -1;
esp_err_t err = i2c_master_transmit(dev, src, len, us_to_ms(timeout_us));
i2c_master_bus_rm_device(dev);
return (err == ESP_OK) ? (int)len : -1;
}
int
mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen,
uint8_t *dst, size_t rlen, uint32_t timeout_us)
{
if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT;
i2c_master_dev_handle_t dev = add_device(unit, addr);
if (!dev) return -1;
esp_err_t err = i2c_master_transmit_receive(dev, src, wlen, dst, rlen,
us_to_ms(timeout_us));
i2c_master_bus_rm_device(dev);
return (err == ESP_OK) ? (int)rlen : -1;
}
+64
View File
@@ -0,0 +1,64 @@
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include <mruby/i2c.h>
#define UNIT_SELECT(u) \
i2c_inst_t *inst; \
switch (u) { \
case 0: inst = i2c0; break; \
case 1: inst = i2c1; break; \
default: return MRB_I2C_ERROR_UNIT; \
}
int
mrb_i2c_unit_name_to_num(const char *name)
{
if (strcmp(name, "RP2040_I2C0") == 0) return 0;
if (strcmp(name, "RP2040_I2C1") == 0) return 1;
return MRB_I2C_ERROR_UNIT;
}
mrb_i2c_status
mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl)
{
UNIT_SELECT(unit);
i2c_init(inst, freq);
if (sda < 0) sda = PICO_DEFAULT_I2C_SDA_PIN;
if (scl < 0) scl = PICO_DEFAULT_I2C_SCL_PIN;
gpio_set_function(sda, GPIO_FUNC_I2C);
gpio_set_function(scl, GPIO_FUNC_I2C);
gpio_pull_up(sda);
gpio_pull_up(scl);
return MRB_I2C_OK;
}
int
mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len,
uint32_t timeout_us)
{
UNIT_SELECT(unit);
return i2c_read_timeout_us(inst, addr, dst, len, false, timeout_us);
}
int
mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len,
uint32_t timeout_us)
{
UNIT_SELECT(unit);
return i2c_write_timeout_us(inst, addr, src, len, false, timeout_us);
}
int
mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen,
uint8_t *dst, size_t rlen, uint32_t timeout_us)
{
UNIT_SELECT(unit);
/* write with nostop=true (no STOP, keeps bus for repeated START) */
int ret = i2c_write_timeout_us(inst, addr, src, wlen, true, timeout_us);
if (ret < 0) return ret;
/* read with nostop=false (STOP after read) */
return i2c_read_timeout_us(inst, addr, dst, rlen, false, timeout_us);
}
+200
View File
@@ -0,0 +1,200 @@
#include <string.h>
#include <mruby.h>
#include <mruby/presym.h>
#include <mruby/variable.h>
#include <mruby/array.h>
#include <mruby/string.h>
#include <mruby/i2c.h>
#define STACK_BUF_SIZE 256
#define E_IO_ERROR mrb_exc_get_id(mrb, MRB_SYM(IOError))
static size_t
i2c_fill_buf(mrb_state *mrb, uint8_t *buf, mrb_value *args, mrb_int argc)
{
size_t pos = 0;
for (mrb_int i = 0; i < argc; i++) {
switch (mrb_type(args[i])) {
case MRB_TT_ARRAY: {
mrb_int alen = RARRAY_LEN(args[i]);
const mrb_value *aptr = RARRAY_PTR(args[i]);
for (mrb_int j = 0; j < alen; j++) {
if (!mrb_integer_p(aptr[j])) {
mrb_raise(mrb, E_TYPE_ERROR, "array element must be Integer");
}
buf[pos++] = (uint8_t)mrb_integer(aptr[j]);
}
break;
}
case MRB_TT_INTEGER:
buf[pos++] = (uint8_t)mrb_integer(args[i]);
break;
case MRB_TT_STRING:
memcpy(&buf[pos], RSTRING_PTR(args[i]), RSTRING_LEN(args[i]));
pos += RSTRING_LEN(args[i]);
break;
default:
break;
}
}
return pos;
}
static size_t
i2c_calc_size(mrb_state *mrb, mrb_value *args, mrb_int argc)
{
size_t total = 0;
for (mrb_int i = 0; i < argc; i++) {
switch (mrb_type(args[i])) {
case MRB_TT_ARRAY:
total += RARRAY_LEN(args[i]);
break;
case MRB_TT_INTEGER:
total += 1;
break;
case MRB_TT_STRING:
total += RSTRING_LEN(args[i]);
break;
default:
mrb_raise(mrb, E_TYPE_ERROR, "Integer, Array, or String expected");
}
}
return total;
}
/* Allocate write buffer, fill it, return pointer and size.
Caller must free if need_free is set. */
static uint8_t*
i2c_build_buf(mrb_state *mrb, mrb_value *args, mrb_int argc,
size_t *out_len, uint8_t *sbuf, mrb_bool *need_free)
{
size_t total = i2c_calc_size(mrb, args, argc);
uint8_t *buf;
if (total <= STACK_BUF_SIZE) {
buf = sbuf;
*need_free = FALSE;
}
else {
buf = (uint8_t*)mrb_malloc(mrb, total);
*need_free = TRUE;
}
i2c_fill_buf(mrb, buf, args, argc);
*out_len = total;
return buf;
}
static mrb_int
get_timeout(mrb_state *mrb, mrb_value self, mrb_value kw)
{
if (mrb_undef_p(kw)) {
return mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(timeout)));
}
return mrb_integer(kw);
}
static mrb_value
mrb_i2c_m_write(mrb_state *mrb, mrb_value self)
{
mrb_value *args;
mrb_int argc, addr;
const mrb_sym kw_names[] = { MRB_SYM(timeout) };
mrb_value kw_values[1];
mrb_kwargs kwargs = { 1, 0, kw_names, kw_values, NULL };
mrb_get_args(mrb, "i*:", &addr, &args, &argc, &kwargs);
mrb_int timeout_ms = get_timeout(mrb, self, kw_values[0]);
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
uint8_t sbuf[STACK_BUF_SIZE];
size_t wlen;
mrb_bool need_free;
uint8_t *buf = i2c_build_buf(mrb, args, argc, &wlen, sbuf, &need_free);
int ret = mrb_i2c_write((int)unit, (uint8_t)addr, buf, wlen,
(uint32_t)timeout_ms * 1000);
if (need_free) mrb_free(mrb, buf);
if (ret < 0) {
mrb_raise(mrb, E_IO_ERROR, "I2C write failed");
}
return mrb_fixnum_value(ret);
}
static mrb_value
mrb_i2c_m_read(mrb_state *mrb, mrb_value self)
{
mrb_value *args;
mrb_int argc, addr, len;
const mrb_sym kw_names[] = { MRB_SYM(timeout) };
mrb_value kw_values[1];
mrb_kwargs kwargs = { 1, 0, kw_names, kw_values, NULL };
mrb_get_args(mrb, "ii*:", &addr, &len, &args, &argc, &kwargs);
if (len <= 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "read length must be positive");
}
mrb_int timeout_ms = get_timeout(mrb, self, kw_values[0]);
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
uint32_t timeout_us = (uint32_t)timeout_ms * 1000;
uint8_t *rxbuf = (uint8_t*)mrb_malloc(mrb, len);
int ret;
if (argc > 0) {
/* write-then-read (repeated START) */
uint8_t sbuf[STACK_BUF_SIZE];
size_t wlen;
mrb_bool need_free;
uint8_t *wbuf = i2c_build_buf(mrb, args, argc, &wlen, sbuf, &need_free);
ret = mrb_i2c_write_read((int)unit, (uint8_t)addr,
wbuf, wlen, rxbuf, (size_t)len, timeout_us);
if (need_free) mrb_free(mrb, wbuf);
}
else {
ret = mrb_i2c_read((int)unit, (uint8_t)addr, rxbuf, (size_t)len, timeout_us);
}
if (ret < 0) {
mrb_free(mrb, rxbuf);
mrb_raise(mrb, E_IO_ERROR, "I2C read failed");
}
mrb_value str = mrb_str_new(mrb, (const char*)rxbuf, ret);
mrb_free(mrb, rxbuf);
return str;
}
static mrb_value
mrb_i2c_m_init(mrb_state *mrb, mrb_value self)
{
const char *unit;
mrb_int freq, sda, scl;
mrb_get_args(mrb, "ziii", &unit, &freq, &sda, &scl);
int num = mrb_i2c_unit_name_to_num(unit);
if (num < 0) {
mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown I2C unit: %s", unit);
}
mrb_i2c_status st = mrb_i2c_init(num, (uint32_t)freq, (int8_t)sda, (int8_t)scl);
if (st != MRB_I2C_OK) {
mrb_raise(mrb, E_IO_ERROR, "I2C init failed");
}
return mrb_fixnum_value(num);
}
void
mrb_hw_i2c_gem_init(mrb_state *mrb)
{
struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(I2C), mrb->object_class);
mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_i2c_m_init, MRB_ARGS_REQ(4));
mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_i2c_m_write, MRB_ARGS_REQ(1)|MRB_ARGS_REST()|MRB_ARGS_KEY(1, 0));
mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_i2c_m_read, MRB_ARGS_REQ(2)|MRB_ARGS_REST()|MRB_ARGS_KEY(1, 0));
}
void
mrb_hw_i2c_gem_final(mrb_state *mrb)
{
}
+87
View File
@@ -0,0 +1,87 @@
# hw-pwm - PWM peripheral interface for mruby
This gem provides the `PWM` class for Pulse Width Modulation
output from mruby. It is designed for embedded platforms such as
ESP32 and RP2040.
## Architecture
Platform-specific HAL implementations are in `ports/` directories:
- `ports/esp32/` - ESP32 using LEDC peripheral
- `ports/rp2040/` - RP2040 using Pico SDK PWM hardware
## Build Configuration
```ruby
MRuby::CrossBuild.new('esp32') do |conf|
conf.ports :esp32
conf.gem core: 'hw-pwm'
end
```
## Ruby API
### PWM.new
```ruby
pwm = PWM.new(pin, frequency: 1000, duty: 50)
```
- `pin` - GPIO pin number (Integer)
- `frequency:` - frequency in Hz (default: 0, disabled)
- `duty:` - duty cycle in percent 0-100 (default: 50)
### PWM#frequency(freq)
Set frequency in Hz. Returns the frequency. Setting 0 disables
output.
```ruby
pwm.frequency(1000) # 1 kHz
pwm.frequency(0) # disable
```
### PWM#period_us(us)
Set period in microseconds. Returns the corresponding frequency.
```ruby
pwm.period_us(1000) # 1ms period = 1 kHz
```
### PWM#duty(pct)
Set duty cycle in percent (0.0-100.0). Clamped to range.
```ruby
pwm.duty(75.0)
```
### PWM#pulse_width_us(us)
Set pulse width in microseconds. Duty cycle is calculated from
current frequency.
```ruby
pwm.pulse_width_us(500) # 500us pulse width
```
## HAL Interface
To add support for a new platform, create a `ports/<name>/`
directory and implement the following C functions declared in
`<mruby/pwm.h>`:
```c
void mrb_pwm_init(uint32_t pin);
void mrb_pwm_set_freq_duty(uint32_t pin, float frequency, float duty);
void mrb_pwm_set_enabled(uint32_t pin, bool enabled);
```
- `frequency` is in Hz, `duty` is in percent (0-100)
- `mrb_pwm_set_enabled` is called with `false` when frequency is 0
## License
MIT
+20
View File
@@ -0,0 +1,20 @@
#ifndef MRUBY_PWM_H
#define MRUBY_PWM_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* HAL functions - implemented in ports/<platform>/pwm.c */
void mrb_pwm_init(uint32_t pin);
void mrb_pwm_set_freq_duty(uint32_t pin, float frequency, float duty);
void mrb_pwm_set_enabled(uint32_t pin, bool enabled);
#ifdef __cplusplus
}
#endif
#endif /* MRUBY_PWM_H */
+5
View File
@@ -0,0 +1,5 @@
MRuby::Gem::Specification.new('hw-pwm') do |spec|
spec.license = 'MIT'
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
spec.summary = 'PWM peripheral interface'
end
+9
View File
@@ -0,0 +1,9 @@
class PWM
def initialize(pin, frequency: 0, duty: 50)
@pin = pin
__init(@pin)
@frequency = frequency.to_f
@duty = duty.to_f
frequency(@frequency)
end
end

Some files were not shown because too many files have changed in this diff Show More