ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

mruby 3.2 で実行できるmrbバイナリを自作した

手作りの温かみのあるバイナリです。

作りかた

そろそろRustで書くか、と思ってRustで書いた。

bytes というcrateが今回の用途にめちゃくちゃマッチしていた。数値をBig Endianで書き込めればいいので、こういう感じで。

    pub fn write(&self, mut out: impl io::Write) -> Result<(), io::Error> {
        let b = &self.binary_header;
        let mut buf = bytes::BytesMut::new();
        buf.put_slice(&b.ident);
        buf.put_slice(&b.major_version);
        buf.put_slice(&b.minor_version);
        let size_pos = buf.len();
        buf.put_i32(-1);
        buf.put_slice(&b.compiler_name);
        buf.put_slice(&b.compiler_version);

        for section in self.sections.iter() {
            match section.header.ident {
                rite::markers::irep => {
                    let h = &section.header;
                    let i = section.body_irep.as_ref().unwrap();

                    buf.put_slice(&h.ident); //....

                    buf.put_u16(i.pool.len() as u16);
                    for pi in i.pool.iter() {
                        match pi {
                            rite::PoolItem::Str(s) => {
                                buf.put_u8(0); // IREP_TT_STR
                                // ...
                            }
                            _ => {
                                todo!("unsupported")
                            }
                        }
                    }

                    buf.put_u16(i.syms.len() as u16);
                    for s in i.syms.iter() {
                        buf.put_u16(s.name.len() as u16);
                        buf.put_slice(&s.name);
                        buf.put_u8(0);
                    }

                    for _ in i.children.iter() {
                        // ...
                    }
                }
                rite::markers::end => {
                    let h = &section.header;
                    buf.put_slice(&h.ident);
                    buf.put_i32(h.size);
                }
                _ => {
                    unreachable!("unknown section ident")
                }
            }
        }

        // finally write back total binsize
        let size = buf.len();
        buf[size_pos] = ((size >> 24) & 0xff) as u8;
        buf[size_pos + 1] = ((size >> 16) & 0xff) as u8;
        buf[size_pos + 2] = ((size >> 8) & 0xff) as u8;
        buf[size_pos + 3] = (size & 0xff) as u8;

        let freeze = buf.freeze();
        out.write(&freeze[..])?;
        out.flush()?;

        Ok(())
    }

あとはサイズの検知の仕方とかをこなれた感じにするかなー。

できたバイナリ、compiler name が MATZ と違っていてもちゃんと実行できる!

$ xxd /opt/ghq/github.com/udzura/mrbasm/sample.mrb        
00000000: 5249 5445 3033 3030 0000 0056 4d41 534d  RITE0300...VMASM
00000010: 3030 3030 4952 4550 0000 003a 3033 3030  0000IREP...:0300
00000020: 0000 002e 0001 0004 0000 0000 0000 000a  ................
00000030: 5102 002d 0100 0138 0169 0001 0000 0548  Q..-...8.i.....H
00000040: 656c 6c6f 0000 0100 0470 7574 7300 454e  ello.....puts.EN
00000050: 4400 0000 0008                           D.....
$ ./bin/mruby /opt/ghq/github.com/udzura/mrbasm/sample.mrb
Hello

コード

github.com

色々ラフですが。一旦上記に置いた。

リポジトリ名の通り、最終的にはmrubyの命令を直接記述できるアセンブリ言語を作ろうとしている。まだ何も設計できていないが、多分こういう感じ。

!binformat mruby 3.2
!begin irep main:
!rootirep
    LOADI_2       R1      (2)
    MOVE          R2      R1
    LOADI_1       R3      (1)
    GT            R2      R3
    JMPNOT        R2      else1
    STRING        R3      "big"
    SSEND         R2      :puts   1
    JMP           end1
else1:
    STRING        R3      "small"
    SSEND         R2      :puts   1
end1:
    RETURN        R2
    STOP
!end

!begin irep sub:
    LOADI_2       R1      (2)
    MOVE          R2      R1
    ...
!end

ただまあ、このasmは正直やろうとしていることに必要だから作っていて、ゴールでもなんでもない。作りたいのはもうちょっと別のもの。

ということでRubyKaigi お疲れ様です。みなさんと沖縄で会えるといいっすね。

mruby 3.2.0 のバイナリフォーマット

なんとなくバイナリを解析してえ〜と思ったので、mruby 3.2.0 (最新stable?)の .mrb ファイルのフォーマットを眺めることにした。

Rustでパースしました!だとかっこいい、ナウだなと思ったけれど、動的型育ちな自分をどうしても甘やかしてしまい、 Rubyunpack を軸に解析した。いやほんと、今やRubyで一番使うメソッドでは。

続きを読む

自作 LSM-Tree その1

Log-Structured Merge Tree というデータ構造があって、データ指向アプリケーションデザインを読んでいるとかなり最初の方に出てくる。Wikipediaの記事の通りLevelDBを始めきょうびのさまざまなデータベース製品で使われている。

特徴はめちゃくちゃざっくり*1

  • 追記型のログを使うことで書き込みの性能を保つ
  • インデックスはキー名とログ内のオフセットzを持っておき、読み出しも速度を出す
  • ログを SSTable というデータ構造に退避することで、インデックスはある程度疎にしつつ(めちゃくちゃキーが多くなってインデックスの時点でデカくなるなどを防ぐ)速度を保ってアクセスできる
  • 必要に応じてマージなどをしてインデックスは最新のデータだけに、小さくする

今回お仕事とか色々でLSMを使うため、コードの理解を助けるためにまずは自分で実装しようと思ったので、久しぶりの連作ブログ記事を始めた。

自分で実装と言いつつ細かいところをオミットしていたり、勘違いもあると思うので、優しくご指摘いただければ...。

そんな感じです。

やること

LSM-Tree indexを持つログ追記型のファイルベースDBを作る。

細かいチューニングは置いておく。

言語はGoです。

*1:この時点で誤解があったら指摘してください...

続きを読む

Cloud BuildからCloud Functionsを呼びたい、それだけなんだ

Google Cloudのサーバレス主要サービスであるCloud BuildCloud Functionsを使っていて、Cloud BuildからCloud Functionsをなるべく安全っぽく呼びたかったのだが、適当な手順がなかなか見つからなかったのでここに残しておき、将来自分が忘れた時に検索可能にしたい。

Prelude: Compute EngineのVMからCloud Functionsを呼ぶ

Cloud FunctionsはCompute EngineのVMから呼ぶのは比較的簡単である。以下は、認証が必要なHTTPS呼び出し設定をしたFunctionsを前提とする。

続きを読む

Lima 0.14 のVirtualization.framework対応を試す

Linux Advent Calendar 2022 19日目の記事です、と言いつつ遅くなりましたが....。

qiita.com

Lima 、使ってますか

Linux Advent Calendar なのにMacの話では?となりそうな感じもありますが、Lima使っていますか? Docker の環境を colima で作っている方もいらっしゃるかもしれません。

Lima を用いるとコマンドラインベースでLinux VMを作成できて便利で、尚且つ複数のCPUアーキテクチャVMを立ち上げられます。Limaの場合、ホストと別アーキテクチャVM作成は、これまでQEMUを利用しており、動作はそれなりのオーバーヘッドがかかるようになっていました。

ですが、Lima 0.14 からVirtualization.framework(vz)に対応したそうです。待望の!

github.com

colima からVirtualization.frameworkを使う

lima >= 0.14 + colima 0.5.0 の組み合わせだと非常にあっさりvzで環境を作れるので、拍子抜けするほど便利です(無論Macのバージョンを 13.0 以降に上げる必要もあります)。

$ colima start --cpu 8 --memory 12 --disk 150 --arch x86_64 --vm-type vz ${profile_name}

以上!と言いたいところですがすごく簡単にベンチを取ってQEMUと比べておきましょう。

vz / QEMU のベンチ

簡単ですが、以下のようなDockerfileを作って Shopify/go-lua をビルドしてベンチマークとしてみました。

# syntax = docker/dockerfile:1.3-labs
FROM golang:1.19

RUN <<SHELL
    git clone https://github.com/Shopify/go-lua.git
    cd go-lua
    git submodule update --init
    go build
    go test -cover
SHELL

ホストの環境はApple M1 Max(8+2 core)、64GB memoryという感じです。

結果です。ビルドのフェーズ2の箇所の所要時間だけ抜粋すると、

vz

 => [2/2] RUN <<SHELL (git clone https://github.com/Shopify/go-lua.git...) ... 20.1s 

QEMU

 => [2/2] RUN <<SHELL (git clone https://github.com/Shopify/go-lua.git...) ... 22.5s

1割ほど高速になっているようです。処理の内容的に、ダウンロード+ビルドでCPUワークロードも雑多な感じなので、参考程度ではありますが。

もうちょっと複雑なソフトウェア(CRubyとか...)をビルドして比べてみたい気持ちもありますが、時間が...。


ということで軽くこんな感じです。 lima 0.14 + colima 0.5.0 のDocker環境はより快適だと思いますので、M1勢試していきましょう。