Text
Page: 1
MicroRuby
True Microcontroller Ruby
hasumikin
RubyKaigi 2025
17th April 2025
Page: 2
Three Proposals for RubyKaigi 2025
MicroRuby (this talk)
PicoRuby.wasm
Keyboard
Page: 3
Contents
Part1: DISCUSSION
Part2: IMPLEMENTATION
Part3: HARD WORK
Page: 4
Acknowledgement
This project is developed under the support
of the Ruby Association Grant 2024 with
Matz advisory
Page: 5
A Sequel of the Grant 2021
Page: 6
Part1: DISCUSSION
Page: 7
mruby for Microcontroller
People make mruby(/c) applications with
their embed-programming skills
But, no friendly microcontroller framework
unlike MicroPython and PicoRuby (R2P2)
Guess why?
Page: 8
mruby for Microcontroller
Making a MicroPython-like ecosystem is
hard
It requires skills of integrating Embedded
systems and Ruby
One Problem Found
Page: 9
One Problem Found
"I'm clumsy and can't do electronics"
Page: 10
One Problem Found
🤔(Why he makes mruby then?)
Page: 11
One Problem Found
💪"
"Anyway, let me do it
Page: 12
PicoRuby and MicroRuby
|
| PicoRuby |
MicroRuby
|
==========================================
| Compiler |
picoruby-compiler
|
------------------------------------------
|
VM
| mruby/c |
mruby
|
------------------------------------------
|
Gems
| Picogems | Picogems + Mgems |
Page: 13
diff mruby..mruby/c (Features)
|
|
mruby
| mruby/c |
========================================================================
| Standard Libraries
| Enough
| Less
|
| Singleton Method
| Yes
| No
|
| Module features
| Yes
| Limited |
| Method Visibility
| Yes (version 3.4+) [new!] | No
|
| Meta-Programming
| Yes
| Less
|
| Able to Call Ruby Method in C | Yes
| No
|
| RVALUE Boxing
| Word or NaN
| No
|
| Presym [*]
| Yes
| No
|
------------------------------------------------------------------------
| Garbage Collector
| Tri-color Incremental GC | Refcount |
| Library Management
| Mrbgem + Rake
| No
|
------------------------------------------------------------------------
| Built-in Task Scheduler [*]
| No
| Yes
|
| Footprint (in general)
| Bigger
| Smaller |
[*] mentioned later
Page: 14
In Short,
mruby’s Footprint is
Bigger than mruby/c
Page: 16
Is mruby Footprint Too Big to Use??
😇
TBH, I am not sure
Page: 17
A Confession
Hello, World! is not what we
want to do in the real world
Page: 18
What Comsumes RAM in Ruby?
Stack and heap objects in C functions
Ruby objects and data in object
data: String, Bignum, and CDATA, etc.
Ruby call stack (callinfo)
IREP
Symbol table
Page: 19
IREP
Memory-developed object of VM code and
literals
An IREP represents a code block
Code block
Top-level code, class, module, def, do…end, {…}
Consists of iseq, literals, nested ireps
Page: 20
Presym is the Key to Win
A unique feature of mruby
Shipped with mruby 3.0 in March 2021
CRuby and mruby/c don’t have presym
Symbol table and IREPs are located in
ROM instead of RAM
Page: 21
One More Key: ObjectSpace
Heap Page management: Each RVALUE
doesn’t contain meta-data and padding
CRuby
mruby
: |meta-data|RVALUE|RVALUE|....................
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Single Allocation
mruby/c : |meta-data+mrbc_value| |meta-data+mrbc_value|
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
Single Allocation
Single Allocation
Page: 22
Hypothesis
mruby’s Presym and Heap Paging would
be keys to small footprint especially,
A big MicroRuby application that bundles
many gems (IREPs) and allocates a lot of
objects (RVALUEs) might be effective than
a PicoRuby application
Page: 23
hasumikin Conjecture
RAM [KB]
|
PP
.............................. MMMMMMMM P
|
M
PP :
|
MMMMMMMM
PP
:
|
M
PP
:
|
MMMMMMMMM
PP
:
|
M
PP
:
|
MM
PP
:
|
MMMMM
PP
:
| M :
PP
| M :
PP
P: RAM consumption of PicoRuby(mruby/c)
| M :
PP
M: RAM consumption of MicroRuby(mruby)
: Kanpai Point
| M
: PP
| M PP
:
|MPP :
:
0 +-----^--------------------------------- --->App scale
0
Hello, World!
[gems & objects]
🍺
🍻
🍻
🍺
Page: 24
Raspberry Pi Pico (W)
: 264 KB
Raspberry Pi Pico 2 (W) : 520 KB
🍻
If the🍻point < 500 KB, MicroRuby could be
If the point < 240 KB, MicroRuby could
replace PicoRuby
an alternative to PicoRuby
Page: 25
Part2:
IMPLEMENTATION
Page: 26
How to Prove MicroRuby Eligible?
Run a big application for microcontroller:
R2P2: Ruby Rapid Portable Platform
Things to do to achieve running R2P2:
Integrate mruby’s presym-build with PicoRuby
Introduce preemptive task scheduling to
mruby
Page: 27
Demo: R2P2
https = Net::HTTPSClient.new("ambidata.io")
adc = ADC.new(:temperature)
now = Time.now - INTERVAL # INTERVAL = 60
while true
now = Time.now
if last + INTERVAL <= now
temp = 27 - (adc.read - 0.706) / 0.001721
puts "#{now} / #{temp} C"
body = JSON.generate({
writeKey: WRITE_KEY,
data: [ { created: now.to_i, time: 1, d1: temp } ]
})
HEADERS["Content-Length"] = body.length
https.post(PATH, HEADERS, body)
last = now
end
sleep_ms 100
end
Page: 28
What We’re Gonna Do Is To
Implement
Task Scheduler
for mruby
Page: 29
Preemptive Task and Fiber
Preemptive Task model:
Shares control impartially by interrupt
Fiber model:
Yields control only to an adjoining Fiber
R2P2, a kind of OS, could be implemented
on top of the preemptive task scheduling
Page: 30
mruby Has Fiber
One fiber corresponds to an mrb_context
mrb_context can be also used to manage
task
We can introduce task scheduling by taking
advantage of mrb_context
Page: 31
Task Scheduler Concept (Pseudo Code)
while true
$mrb.tick += 1
if ($mrb.current_task.timeslice -= 1) == 0 # timeslice 10 to 0
$mrb.switch = true
end
sleep_ms 1
end
#---------------
IRQ and
mruby run parallel ---------------#
task_queue = TaskQueue.new # Array-like object
task_queue << task1 << task2 # << task3 ...
while true ##########################################
task_queue.find_next.run do |task|
#
while true ##############################
# Task
execute_iseq(task.current_iseq.shift) # VM
# scheduler
break if $mrb,switch || task.raised? # loop # loop
end end end
#
👆
👇
Page: 32
Tick Counter
// Called every 1 ms
void mrb_tick(mrb_state *mrb)
{
tick_++;
...
current_task->timeslice--;
// ^^^^^^^^^ starts from 10
if (current_task->timeslice == 0) {
mrb->task.switching = TRUE;
}
...
}
Page: 33
mruby VM Loop (Original)
mrb_value mrb_vm_exec(...)
{
while (1) {
insn = BYTECODE_DECODER(*ci->pc);
switch (insn) {
case OP_MOVE: { regs[a] = regs[b]; break; }
case OP_xxxx: { ...; break; }
...
case OP_STOP: { return regs[irep->nlocals]; }
}
}
}
Page: 34
mruby VM Loop (Hacked)
mrb_value mrb_vm_exec(...)
{
while (1) {
insn = BYTECODE_DECODER(*ci->pc);
switch (insn) {
case OP_MOVE: { regs[a] = regs[b]; break; }
case OP_xxxx: { ...; break; }
...
case OP_STOP: { return regs[irep->nlocals]; }
}
if (mrb->task.switching || mrb->exc) { // new
return mrb_nil_value();
// new
}
// new
}
}
Page: 35
Task Scheduler Wraps VM Loop
mrb_value mrb_tasks_run(mrb_state *mrb)
{
while (1) { // Task scheduler loop
...
// VM loop entry point
mrb_vm_exec(mrb, mrb->c->ci->proc, mrb->c->ci->pc);
...
current_task->state = TASKSTATE_READY;
hal_disable_irq();
q_delete_task(mrb, current_task);
q_insert_task(mrb, current_task);
hal_enable_irq();
...
continue;
}
}
Page: 36
Mark Contexts in GC Root Scan Phase
/* mruby/src/gc.c */
static void
root_scan_phase(mrb_state *mrb, mrb_gc *gc)
{
...
if (mrb->c_list) {
for (int i = 0; i < mrb->c_list_len; i++) {
mark_context(mrb, mrb->c_list[i]);
}
}
...
}
Page: 37
Part3: HARD WORK
Page: 38
Port Picogems for MicroRuby
mruby-compiler2 - mruby compiler using a universal parser
picoruby-adc - ADC class / General peripherals
picoruby-base16 - Base16 for PicoRuby
picoruby-base64 - Base64 for PicoRuby
picoruby-bin-microruby - (microruby|picoruby) command
picoruby-bin-r2p2 - R2P2 executable for POSIX
picoruby-cyw43 - CYW43 class
picoruby-editor - Library for editor-like application
picoruby-env - ENV
picoruby-filesystem-fat - FAT filesystem
picoruby-gpio - GPIO class / General peripherals
picoruby-i2c - I2C class / General peripherals
picoruby-io-console - IO class
picoruby-machine - Machine class
picoruby-mbedtls - Mbed-TLS porting for PicoRuby
picoruby-net - Network functionality for PicoRuby
picoruby-picorubyvm - PicoRubyVM class
picoruby-pwm - PWM class / General peripherals
picoruby-rng - Random Number Generator for PicoRuby
picoruby-sandbox - Sandbox class for shell and picoirb
picoruby-shell - PicoRuby Shell library
picoruby-spi - SPI class / General peripherals
picoruby-uart - UART class / General peripherals
picoruby-vfs - Virtual-File-System-like wrapper for filesystems
picoruby-vim - vim-like text editor
picoruby-watchdog - Watchdog class
Page: 39
Hard Work (as of 13 April)
# mruby/mruby
$> git diff --stat master
include/mruby.h | 22 ++++++++++++++++++++++
src/gc.c
| 8 ++++++++
src/vm.c
| 30 +++++++++++++++++++++++++++++-
3 files changed, 59 insertions(+), 1 deletion(-)
# picoruby/picoruby
$> git diff --shortstat master
237 files changed, 12621 insertions(+), 5445 deletions(-)
Page: 40
R2P2 on MicroRuby
🎉
Page: 41
How to Compare RAM Consumption?
Finally, R2P2 on MicroRuby works
Now we want to compare the RAM
consumption on microcontroller
We no longer use glibc’s allocator and
valgrind
Page: 42
How to Compare RAM Consumption?
|
| Default Allocator | Algorithm |
===========================================
| mruby |
Libc (newlib)
| dlmalloc |
-------------------------------------------
| mruby/c |
Built-in
|
TLSF
|
Not fair to compare different allocators
newlib doesn’t have a measure method
Might as well make a TLSF (Two-Level
Segregated Fit) allocator for mruby?
Page: 43
Make an Allocator
Page: 44
ESTALLOC
https://github.com/picoruby/estalloc
Derived from mruby/c’s built-in allocator
Originally only supports 4-byte alignment
Improved to support alignment options:
4-byte and 8-byte
Page: 45
Let’s See
( 165716 - (2237 * 24) ) / 1024 ~= 109 KB
^^^^^^
^^^^
^^
^^^^^^
Allocated
Free sizeof(RVALUE) Actual Used
Page: 46
R2P2 RAM Consumption
| R2P2 on PicoRuby (initial)
| 49 KB |
| R2P2 on PicoRuby (gems loaded) | 78 KB |
-------------------------------------------
| R2P2 on MicroRuby (presymed)
| 109 KB |
PicoRuby will use more RAM by adding
gems
Page: 47
hasumikin Conjecture
RAM [KB]
|
PP
.............................. MMMMMMMM P
|
M
PP :
|
MMMMMMMM
PP
:
|
M
:
PP
:
|
MMMMMMMMM
: PP
:
|
M
PP
:
|
MM
PP :
: P: PicoRuby
|
MMMMM
PP
:
: M: MicroRuby
| M :
PP
:
:
| M :
PP
:
:
| M :
PP
:
:
| M
: PP
:
:
| M PP
:
:
|MPP :
:
:
0 +-----^--------------------^------------ --->App scale
0 Hello, World!
R2P2 (at init)
? ?
🍺
🍻
🍺
🤔
Page: 48
Practicality
|
| RAM | PicoRuby | MicroRuby |
=================================================
| Raspi Pico
| 264KB |
|
|
| Raspi Pico W
| 264KB |
|
|
| Raspi Pico 2
| 520KB |
|
|
| Raspi Pico 2 W | 520KB |
|
|
✅
✅
✅
✅
✅
❌
✅
✅
W : WiFi and BLE module
You need to share RAM for these stacks like
MbedTLS and LwIP
Page: 49
Released!!! (with Bugs)
Page: 51
Wrap Up
🍻
(Soon) You can easily write IoT apps with
almost full-spec Ruby🎊
R2P2 on MicroRuby runs within a
reasonable size of RAM
Page: 52
What If PicoRuby Introduces Presym?
🍺
RAM [KB]
|
M
|
MMMMMMMM
|
M
|
MMMMMMMM
|
M
|
MMMMMMMMM
No Kanpai Anymore
|
M
|
MM
|
MMMMM
PPPPPP
| M
PPPPPP
| M
PPPPPP
| M
PPPPPP
| M
PPPPPP
| M PPPPPP
|MPP
0 +-------------------------------------------->App scale
0
[gems & objects]
😭
🍺
Page: 53
Coming Up Title Would Be
MAKE PICORUBY
SMALL AGAIN
(not serious)
Page: 54
n-Monthly Lambda Note Vol.4, No.1
WebAssemblyの
制約を越える
@kateinoigakukun
PicoRubyといっしょに
学ぶ、プログラミング
言語が電気回路を動かす
仕組み @hasumikin
Autograph Session Right after This Talk!
@.bookstore on the 2nd floor
Page: 55
Rubyではじめる電子工作
副題: ラジコンを作ろう
@hachi
n月刊ラムダノート
(前頁)が理論編なら
こちらが実践編
Only at RubyKaigi :
Get Both Books!