Rabbit Slide Show

mruby machine - An Operating System for Microcontroller

2020-09-04

Description

Presentation slide for RubyKaigi Takeout 2020

Text

Page: 1

mruby machine
An Operating System for Microcontroller
HASUMI Hitoshi @hasumikin
September 4-5, 2020

Page: 2

Me
HASUMI Hitoshi(羽角 均)
@hasumikin(ハスミキン)
From Ruby City Matsue,
a holy place of
(熱燗)
Developer on Rails
(レールに乗った開発者)
Coffee
Curry
Soba (NoEmojiError)

Page: 3


        

Page: 4

Chapter 1
Background

Page: 5

Motivation
MicroPython
Generally, we keep minimal configuration of
MicroPython under 80K of ARM Thumb2 code
(which includes compiler and interactive
prompt, ...). That means a Cortex-M
microcontroller with 128KB of flash can host a
minimal version together with some hardware
drivers.
[cited from `https://github.com/micropython/micropython/wiki/FAQ']

Page: 6

Motivation
MicroPython
Regarding RAM usage, (...) 8KB is minimal
amount to run simple scripts. As Python is
interpreted high-level language, the more
memory you have, the more capable
applications you can run. The reference
MicroPython board, PyBoard, has 128KB of RAM.
[cited from `https://github.com/micropython/micropython/wiki/FAQ']

Page: 7

Motivation
mruby and mruby/c
Flashing mruby application into 128KB ROM and
running it on 128KB RAM are difficult
mruby/c runs on 64KB RAM but it doesn't have
mruby compiler
You have to compile Ruby script into VM code before
embedding

Page: 8

Motivation
MicroPython:
"Overwhelming, we are!"
「圧倒的じゃないか、我が軍は」

Page: 9

Motivation
Would it be great if we have a
smaller mruby compiler?

Page: 10

Target Microcontroller
PSoC5LP: Reference board of mruby/c
Arm Cortex-M3 32bit processor
64KB RAM
256KB ROM
It will be comparable to MicroPython if Ruby runs
on PSoC5LP with compiler

Page: 11

Demo

Page: 12

Chapter 2
Terms and Tools

Page: 13

Terms
mruby
A Ruby implementation for general embedded purpose
mrbc
The original mruby compiler bundled with mruby
mruby/c
Another implementation of mruby VM for one-chip
microcontroller
It doesn't have compiler

Page: 14

Terms
mmrbc (mini mruby compiler)
Another mruby compiler for one-chip microcontroller
mmruby (mini mruby) = mmrbc + mruby/c
mruby machine
A microcontroller embedded with integration of
mmruby, CUI shell and peripheral drivers

Page: 15

mruby machine

Page: 16

Cross compilation tools for bare metal Arm
arm-none-eabi-gcc, arm-none-eabi-ar, arm-none-
eabi-ld, arm-none-eabi-strip, etc.
What is "arm-none-eabi-" ?
arm: Arm architecture
none: Without OS (Bare metal)
eabi: Embedded Application Binary Interface

Page: 17

Three tool-chains in my laptop (Intel 64)
1. (x86_64-linux-gnu-)gcc
Usual compilation in daily development
2. arm-linux-gnueabi-gcc
Binary runs on QEMU (machine emulator)
3. arm-none-eabi-gcc (--with-newlib)
To make a final library archive (libmmrbc.a) for
bare metal Arm

Page: 18

Docker image
((‘tag:center’))
https://hub.docker.com/r/
hasumikin/arm-none-eabi-tools

Page: 19

C Library
glibc (GNU C Library)
Newlib
Another C library especially for embedded system
Some implementation is missed. eg) regex.c
Newlib-nano
Even smaller version of Newlib for Arm processor
Yet other libraries
EGLIBC, Clibc, sglibc, etc.

Page: 20

Chapter 3
mmrbc

Page: 21

Repo
https://github.com/hasumikin/mmruby

Page: 22

Features of mmrbc
Portable and Less dependent
Doesn't depend on mruby nor mruby/c
(To the contrary, mrbc depends on mruby itself)
Depends on only standard C library including Newlib(-
nano)
Can be integrated with both mruby and mruby/c

Page: 23

Features of mmrbc
Small (by linking to Newlib-nano)
Fits in less than 256KB ROM (including mruby/c VM
and some application code)
Runs enough on less than 128KB RAM

Page: 24

Features of mmrbc
Lemon as a parser generator instead of Yacc/Bison
Originally a parser generator of SQLite

Page: 25

Yacc/Bison
LALR(1) parser generator, typical choice of parser
generator
Yacc (Barkley Yacc)
Non-reentrant
Parser-Calls-Lexer style (CRuby and mruby use it)
Bison (Gnu Bison. Modernized version of Yacc)
Reentrant, Thread-Safe and Lexer-Calls-Parser style
(You can also select old Yacc style)

Page: 26

Lemon
Reentrant
Lexer-Calls-Parser style

Page: 27

Parser-Calls-Lexer style (Yacc)
yyparse(p);
/* in y.tab.c (generated by Yacc) */
int yyparse(parser_state *p) {
yytoken = yylex(yylval, p);
/* makes AST according as you defined */
...
goto yyhogefuga;
...
}
/* yylex() is called by parser */
int yylex(void *lval, parser_state *p) {
# Find next token and return token type
return tokenType;
}
GenerateVMCode(p->ast);

Page: 28

Lexer-Calls-Parser style (Lemon)
/* in your own Lexer */
while (hasMoreToken(rubyScript)) {
token = getToken(rubyScript);
Parse(parser, tokenType, token);
}
Parse(parser, 0, "");
/* in parse.c (generated by Lemon) */
void Parse(void *yyp, int yymajor, ...) {
/* makes AST according as you defined */
...
}
GenerateVMCode(p->ast);

Page: 29

Lemon
LALR(1) parser generator as well
Reentrant
Lexer-Calls-Parser style
Generated parser is, I'm still not sure though,
Faster than Yacc/Bison (according to its document)
Smaller code (according to someone)
Grammar syntax of parse.y is different from Yacc/
Bison

Page: 30

Lemon
https://sqlite.org/src/doc/trunk/doc/lemon.html

Page: 31

Chapter 4:
How smaller than mrbc?

Page: 32

RAM consumption
mruby's branch
`mruby3` as of August 14th

Page: 33

RAM consumption
To make things simple, both mrbc and mmrbc are
built for x86_64 architecture
using malloc() and free() of glibc
(mmrbc usually uses mrbc_alloc() to manage heap area)
Linux file system to load Ruby script
(microcontroller doesn't have file system)
As a result, we can simply compare with valgrind

Page: 34

RAM consumption
Analysis command looks like:
valgrind \
--tool=massif \
--stacks=yes \
path/to/(mrbc|mmrbc) path/to/script.rb
Showing the result:
ms_print massif.out.xxxx | less

Page: 35

RAM consumption: Case 1
Ruby code:
# hello_world.rb
puts "Hello World!"
Output:
Hello World!

Page: 36

RAM consumption: Case 1 / mrbc
--------------------------------------------------------------------------------
Command:
../mruby/build/host/bin/mrbc ./hello_world.rb
Massif arguments:
--stacks=yes
ms_print arguments: massif.out.4087
--------------------------------------------------------------------------------
KB
157.8^
#
|
#
|
#
|
#
|
#
|
@#
|
@#
|
@#
|
@@@#
|
@@:::::::::::@:::::::@ @#
|
@:::::::@ :::: :: :: @: :: ::@ @#
|
:::::@:::::@: :: ::@ :::: :: :: @: :: ::@ @#
|
::::::::::::::::::::::: : @:: ::@: :: ::@ :::: :: :: @: :: ::@ @#
|
::::: ::: ::: ::: : : ::: : @:: ::@: :: ::@ :::: :: :: @: :: ::@ @#
|
: ::: ::: ::: ::: : : ::: : @:: ::@: :: ::@ :::: :: :: @: :: ::@ @#
|
: ::: ::: ::: ::: : : ::: : @:: ::@: :: ::@ :::: :: :: @: :: ::@ @#
|
: ::: ::: ::: ::: : : ::: : @:: ::@: :: ::@ :::: :: :: @: :: ::@ @#
|
: ::: ::: ::: ::: : : ::: : @:: ::@: :: ::@ :::: :: :: @: :: ::@ @#
|
: ::: ::: ::: ::: : : ::: : @:: ::@: :: ::@ :::: :: :: @: :: ::@ @#
|
: ::: ::: ::: ::: : : ::: : @:: ::@: :: ::@ :::: :: :: @: :: ::@ @#
0 +----------------------------------------------------------------------->Mi
0
3.235

Page: 37

RAM consumption: Case 1 / mmrbc
--------------------------------------------------------------------------------
Command:
../mmruby/build/host-production/bin/mmrbc ./hello_world.rb
Massif arguments:
--stacks=yes
ms_print arguments: massif.out.1652
--------------------------------------------------------------------------------
KB
11.17^
#
|
@#
|
@@# ::
|
@@#:::::
|
@
@ :@: : :: @@#:::::
|
@ :::@: :@::: :::@@#:::::
|
@ :::@: :@:::@:::@@#:::::
|
@:::::@:::@:::@:::@@#:::::
|
@: :::@:::@:::@:::@@#:::::
|
::
@: :::@:::@:::@:::@@#::::::
|
::
@: :::@:::@:::@:::@@#::::::
|
:: :
@: :::@:::@:::@:::@@#::::::
|
:: :
@: :::@:::@:::@:::@@#::::::
|
::::
@: :::@:::@:::@:::@@#::::::
|
::::
@: :::@:::@:::@:::@@#::::::
|
::::
@: :::@:::@:::@:::@@#::::::
|
::::
@: :::@:::@:::@:::@@#::::::
|
::::
@: :::@:::@:::@:::@@#::::::
|
::::
::::::::::::::: @:::::@::: @: :::@:::@:::@:::@@#::::::
|
::::::::::::: :: : :::::::::@:::::@::: @: :::@:::@:::@:::@@#:::::@
0 +----------------------------------------------------------------------->ki
0
224.5

Page: 38

RAM consumption: Case 1 / mmrbc
--------------------------------------------------------------------------------
Command:
../mmruby/build/host-production/bin/mmrbc ./hello_world.rb
Massif arguments:
--stacks=yes
ms_print arguments: massif.out.1652
--------------------------------------------------------------------------------
KB
11.17^
#
|
@#
|
@@# ::
|
@@#:::::
|
@
@ :@: : :: @@#:::::
|
@ :::@: :@::: :::@@#:::::
|
@ :::@: :@:::@:::@@#:::::
|
@:::::@:::@:::@:::@@#:::::
|
@: :::@:::@:::@:::@@#:::::
|
::
@: :::@:::@:::@:::@@#::::::
|
::
@: :::@:::@:::@:::@@#::::::
|
:: :
@: :::@:::@:::@:::@@#::::::
|
:: :
@: :::@:::@:::@:::@@#::::::
|
::::
@: :::@:::@:::@:::@@#::::::
|
::::
@: :::@:::@:::@:::@@#::::::
|
::::
@: :::@:::@:::@:::@@#::::::
|
::::
@: :::@:::@:::@:::@@#::::::
|
::::
@: :::@:::@:::@:::@@#::::::
|
::::
::::::::::::::: @:::::@::: @: :::@:::@:::@:::@@#::::::
|
::::::::::::: :: : :::::::::@:::::@::: @: :::@:::@:::@:::@@#:::::@
0 +----------------------------------------------------------------------->ki
0
224.5
8888

Page: 39

RAM consumption: Case 2
Ruby code:
# idiotic_script.rb
ary = Array.new(3)
ary[0] = {a: 123e2}
ary[0][:key] = "string"
ary[1] = %w(abc ABC Hello)
ary[2] = 0x1f
ary[3] = !true
puts "my name is #{self.class}, result is #{ary[2] * ary[0][:a]}"
p ary

Page: 40

RAM consumption: Case 2
Output:
my name is Object, result is 381300
[{:a=>12300, :key=>"string"}, ["abc", "ABC", "Hello"], 31, false]

Page: 41

RAM consumption: Case 2 / mrbc
--------------------------------------------------------------------------------
Command:
../mruby/build/host/bin/mrbc ./idiotic_script.rb
Massif arguments:
--stacks=yes
ms_print arguments: massif.out.4133
--------------------------------------------------------------------------------
KB
162.9^
##
|
#
|
@#
|
@#
|
@#
|
@#
|
@:@#
|
@:@#
|
@:@# :
|
:::@@::::::@@@@:@# :
|
:::::::@::::::::::@ :: :::@ @:@# :
|
::::@@:::::@ :: ::@:: ::: :::@ :: :::@ @:@# :
|
::::::::::::: :@ :::: @ :: ::@:: ::: :::@ :: :::@ @:@# :
|
::::::::::::: :: :: ::: :@ :::: @ :: ::@:: ::: :::@ :: :::@ @:@# :
|
:: :::: ::::: :: :: ::: :@ :::: @ :: ::@:: ::: :::@ :: :::@ @:@# :
|
:: :::: ::::: :: :: ::: :@ :::: @ :: ::@:: ::: :::@ :: :::@ @:@# :
|
:: :::: ::::: :: :: ::: :@ :::: @ :: ::@:: ::: :::@ :: :::@ @:@# :
|
:: :::: ::::: :: :: ::: :@ :::: @ :: ::@:: ::: :::@ :: :::@ @:@# :
|
:: :::: ::::: :: :: ::: :@ :::: @ :: ::@:: ::: :::@ :: :::@ @:@# :
|
:: :::: ::::: :: :: ::: :@ :::: @ :: ::@:: ::: :::@ :: :::@ @:@# :
0 +----------------------------------------------------------------------->Mi
0
3.470

Page: 42

RAM consumption: Case 2 / mmrbc
--------------------------------------------------------------------------------
Command:
../mmruby/build/host-production/bin/mmrbc idiotic_script.rb
Massif arguments:
--stacks=yes
ms_print arguments: massif.out.13414
--------------------------------------------------------------------------------
KB
53.20^
#
|
#
|
@# :::
|
@# : :
|
@# : :
|
@@# : :
|
@@# :: :
|
@@#::: :
|
@@#::: :
|
@@::@@#::: :
|
@@: @@#::: :
|
@@:@:::@::::@@: @@#::: ::
|
@::@::@ :@:: @:: :@@: @@#::: ::
|
@:::@@@:@::@: @ :@:: @:: :@@: @@#::: ::
|
@@:@::@:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
|
::@@@ :@: @:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
|
:::::: :::::@ @ :@: @:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
|
:::::@@:: :::::: ::@ @ :@: @:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
|
: :: @ :: :::::: ::@ @ :@: @:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
|
: :: @ :: :::::: ::@ @ :@: @:: @@ :@::@: @ :@:: @:: :@@: @@#::: ::
0 +----------------------------------------------------------------------->Mi
0
1.616

Page: 43

RAM consumption: Summary
━━━━━━━━━━┳━━━━━┳━━━━━
Ruby script
┃ mrbc ┃ mmrbc
━━━━━━━━━━╋━━━━━╋━━━━━
hello_world.rb
┃ 157.8KB ┃ 11.17KB
━━━━━━━━━━╋━━━━━╋━━━━━
idiotic_script.rb ┃ 162.9KB ┃ 53.20KB
━━━━━━━━━━┻━━━━━┻━━━━━
Note: These results are larger than possible results on
32bit architecture because of pointer size

Page: 44

Idiotic script
# idiotic_script.rb
ary = Array.new(3)
ary[0] = {a: 123e2}
ary[0][:key] = "string"
ary[1] = %w(abc ABC Hello)
ary[2] = 0x1f
ary[3] = !true
puts "my name is #{self.class}, result is #{ary[2] * ary[0][:a]}"
p ary
=>
my name is Object, result is 381300
[{:a=>12300, :key=>"string"}, ["abc", "ABC", "Hello"], 31, false]
Syntax that mmrbc can compile

Page: 45

Syntax
implemented
lvar, $gvar, @ivar, CONSTANT
assignment, fcall, method chain
Literals: Array, Hash, String, Fixnum, Float, etc.
Keywords: true, false, nil, return, self, etc.
+, -, *, /, %, **, +=, -=, *=, /=, %=, &=, |=, ^=, **=, <<=, >>=
!, ~, +, -, not, &, |, ^, <<, >>
Syntax sugar: %w(a b c)
Interpolation: "The answer is #{get_result(@attr)}!"

Page: 46

Syntax
under construction
if then elif else end, unless
case when else then
while, until, break, next, redo, retry, for in
[0,1].each do |var| end
def method_name(arg); end
class MyClass < ParentClass; end
rescue, ensure
..., etc.

Page: 47

Roadmap
https://github.com/hasumikin/mmruby/issues/6

Page: 48

Code size (to estimate ROM consumption)
% ls -l
(...)
.rwxrwxrwx
.rwxrwxrwx
.rwxrwxrwx
.rwxrwxrwx
609k
903k
650k
425k
hasumi
hasumi
hasumi
hasumi
17
17
17
17
Aug
Aug
Aug
Aug
7:03
7:03
7:03
7:03
mruby_machine_PSoC5LP.a
mruby_machine_PSoC5LP.elf
mruby_machine_PSoC5LP.hex
mruby_machine_PSoC5LP.map
% size mruby_machine_PSoC5LP.elf
text
data
bss
dec
hex filename
136080
320 63009 199409 30af1 mruby_machine_PSoC5LP.elf

Page: 49

Code size (to estimate ROM consumption)
% ls -l
(...)
.rwxrwxrwx
.rwxrwxrwx
.rwxrwxrwx
.rwxrwxrwx
609k
903k
650k
425k
hasumi
hasumi
hasumi
hasumi
17
17
17
17
Aug
Aug
Aug
Aug
7:03
7:03
7:03
7:03
mruby_machine_PSoC5LP.a
mruby_machine_PSoC5LP.elf
mruby_machine_PSoC5LP.hex
mruby_machine_PSoC5LP.map
% size mruby_machine_PSoC5LP.elf
text
data
bss
dec
hex filename
136080
320 63009 199409 30af1 mruby_machine_PSoC5LP.elf
136_080 + 320 = 136_400 ≒ 133.2KB

Page: 50

Summary
mmrbc is a mini mruby compiler
VM code runs on both mruby VM and mruby/c VM
Still incomplete regarding syntax and memory
efficiency
mruby machine
= mmrbc + mruby/c + shell + dirivers
Runs on PSoC5LP { RAM: 64KB, ROM: 256KB }

Page: 51

Thank you!

Other slides