RubyでOSやGCを書く野望

現在、mmcというmrubyをC(に限らないのですが)に変換するプログラムを書いています。抽象実行とエスケープ解析で、GCのサポートや、selfが常に渡されることなどを除いて、手書きのCに近いコードが出てきます。これを使ってOSが書けないかと思い、色々試しています。
もちろん、普通のRubyでは書けないですけどいろいろ拡張して出来そうな気がしてきました。その拡張の詳細を忘れに用にメモしておきます。

# 拡張のためのモジュール
OSを書くための拡張はMMC, HALと2つのモジュールに全て閉じ込めてあります。名前空間は汚したくないですから。

## MMCモジュール
　mmcのメタ情報やコード生成の制御をおこなうためのメソッドやクラスが入っています。

### MMC::attribute
 メソッドどの属性を設定する。gccの__attribute__やRustのメソッド定義の前にある#[...]みたいなやつ。今の所、sectionしかサポートしていない。生成したメソッドを特別なアドレスを割り当てるために使う。
例

```ruby
  MMC::attribute(:section, "BOOT")
  def boot
    cpu = HAL::CPU.new(0)
```

### MMC::class_sizeof(クラス)　未実装
 そのクラスのインスタンスのサイズを返す。単位はバイト

### MMC::instance_sizeof(オブジェクト)　未実装
 オブジェクトのサイズを返す。単位はバイト

### MMC::offsetof(オブジェクト, インスタンス変数)　未実装
 オブジェクト内のインスタンス変数のオフセットを返す。インスタンス変数はシンボル

## HALモジュール
　ハードウエアに依存したクラスなどを閉じ込めたモジュール

### HAL::CPUクラス
　CPUを抽象化したクラス。インストラクションセットを生成するメソッドや、CPUに紐付けされたメモリやレジスタのオブジェクトを持つ。CPUクラスのインスタンスをえるにはCPU番号が必要。これはマルチCPU(しかもヘトロなシステム)に対応するため。

#### CPU#regsメソッド
　CPUのレジスタセットを抽象化したRegsクラスを返す。レジスタ１つではなくCPUにあるレジスタ全部であることに注意。

#### CPU#memメソッド
　CPUのアドレス空間を抽象化したMemクラスを返す。

#### CPU#jmp, CPU#labelメソッド
　ジャンプ命令やラベルの定義を生成するメソッド

### Regsクラス
CPUのレジスタセットを抽象化したクラス。レジスタではなくレジスタ全体にするのは単なる代入ではフックできないが、[]=をオーバーライドするのは色々カスタマイズできるから。

#### Regs#\[](name) メソッド
nameの名前のレジスタに格納された値を返す、のだが返しても大抵の場合意味が無いのでアセンブラのコード生成のために使う。次に説明する[]=()メソッドと一緒に使う

#### Regs#\[]=(name、val)　メソッド
nameの名前のレジスタに値を設定する。とても限定された場合しか使えないが、アセンブラを直接生成する。たとえば、

```ruby
  regs[:eax] = regs[:eax] + regs[:ebx]
```
と書くと、

```nasm
  add EAX, EBX
```

と生成される。(実際はgasのAT&Tフォーマットだけどなじみが多いだろうからIntelフォーマットで説明した)

### Memクラス
CPUのメモリ(正確には論理アドレス空間)を抽象化したもの。あたかも配列のようにメモリを見せたり、メモリ空間の設定をしたりする。

#### Mem#\[address, size = nil]
　　addressが整数(またはアドレス用のクラスを用意する予定)ならそのアドレスの内容。sizeが指定されたらそのバイト数。デフォルトはintと同じ大きさ。
addressがRegクラス(Regs#[]メソッドの戻り値)ならアセンブラの命令を生成する。

```ruby
  regs[:eax] = mem[regs[:eax] + 4]
```

だと、

```nasm
  mov EAX, 4(EAX)
```

というコードが生成される。

#### Mem#\[]=(address, size = nil, val)

 addressとvalが整数ならaddressに示されるメモリを書きこむ。addressやvalがRegクラスだとアセンブラを生成する。

#### Mem#static_cast(address, klass)
addressに示されるメモリ領域をklassクラスのインスタンスとして返す。返って来たオブジェクトは普通のオブジェクトのように使えるけど(initializeメソッドは呼び出されない)、GCのことは全く考えていないので注意。

#### Mem#static_allocate(klass)
klassクラスのインスタンスを静的に確保して(Cレベルではグローバル変数を定義して)、そのオブジェクトを返す。返ってきたオブジェクトは普通のオブジェクトの様に使えるけど　(以下同文)。

#### Mem#static_array_allocate(klass, num)
klassクラスのインスタンスnum個を静的に確保して(Cレベルではグローバル変数を定義して)、配列オブジェクトとして返す。返ってきたオブジェクトは普通のオブジェクトの様に使えるけど　(以下同文)
MMCではエスケープしない配列はCレベルの配列と同じフォーマットです。

#　プログラム例

例えばK&Rにあるような簡単なメモリアロケータを考えてみましょう。こんな感じになるかなと思います。(まだ動かないです)

```ruby
  class Header
    def initiaize
       @size = 0
       @next = 0  # NULL pointer
    end
    attr_accessor :size
    attr_accessor :next
  end

class Allocator
  def initialize
    cpu = HAL::CPU.new(0)  # 0はCPU番号
    mem = cpu.mem

    arena = mem.static_array_allocate(Fixnum, 65536) # アロケートのためのメモリ領域
    header = mem::static_cast(arena, Header)
    header.size = 65536
    header.next = 0
    @root = header
  end

  def malloc(klass)  #このmallocはサイズではなくクラスのインスタンスを返す
    cpu = HAL::CPU.new(0)  # 0はCPU番号
    mem = cpu.mem

    size = MMC::sizeof(klass)
    cchunk = @root
    pchunk = nil
    while cchunk and cchunk.size < size then
      pchunk = cchunk
      cchunk = cchunk.next
    end

    result = nil
    if cchunk then
      result = mem::static_cast(cchunk, klass)   #チャンクの先頭を切りだす
      nchunk = mem::statioc_cast(cchunk + size, Header) #新しいヘッダを作る。前のチャンクは
　　  nchunk.size = cchunk.size - size　#ヘッダのサイズを切りだし
      nchunk.next = cchunk.next　　　　 # 次のチャンクは変らない
      if pchunk then   #番兵を用意した方がいいかも
        pchunk.next = nchunk
      else
        @root = nchunk
      end
    end
    result
  end
end
```