コンパイラの整理 の1 : 言語の概要

とりあえず、現段階のコンパイラは以下に集中する :

  • 構造体アクセスの生成
    • bit-IRの生成
    • packing (SIMDレジスタを活用するように纏めてロード/ストアする)
  • 制御コードの生成
    • Graph-IRの生成
    • プレディケーションによるselectをIFに戻す
    • Basic Blockの関数化

intrinsicsを使ったCコードを生成し、実際のコード生成やレジスタ割り付け、命令スケジューリング等はコンパイラに任せる。
データ取りをするためには、これらの処理パラメタを外部から与えたり差し替えたりできるようにするのが望ましい。現状は、リストを線形探索してて異常に遅いので、その辺を抽象化する形で書き直す。

define-packet-format

bit-IRやword-IRは、define-packet-formatから生成される。例えばTCPヘッダだと以下のような定義を書く。

(define-subtype tcp-port u16)
(define-subtype flag u1)
(define-packet-format tcp-header
   (source-port tcp-port)
   (destination-port tcp-port)
   (sequence-number u32)
   (acknowledgement-number u32)
   (data-offset nibble)
   (reserved)
   (URG flag)
   (ACK flag)
   (PSH flag)
   (RST flag)
   (SYN flag)
   (FIN flag)
   (@ -- u16)
   (window u16)
   (checksum u16)
   (urgent-pointer u16)
   (tcp-options *)
   (tcp-data))

無名として@、アラインメントとして--、単一型リストとして*がそれぞれ使える。複合型はdefine-unionで予め一つの型に纏める必要がある(糖衣構文を導入予定)。
意味のある型を付けることが推奨される。tcp-port等が例。これは単にu16だが、パケットダンプを意味のある情報に出来る。
nibbleやbyte、wordはsubtypeで、元の型はそれぞれu4、u8、u16のように書かれる。
アラインメントは特殊構文で、パディングに名前を付ける事も出来る。特殊なパディング(先詰等)は新たにpacket-formatを定義しなければならない。パディングは関連づけられた型整数個で表されるように入れられる。
bit-fill rulesは廃止した。現在、通常のbig-endianとlittle-endian(従来のbyte-pack)をハードコードして対応している。例えばUSBの物理信号を表現したいというような需要が有るなら、もっと柔軟なシステムが必要になる。
packet-formatは引数を持つことができる(通常の関数のようにdefineする)が、現在は使っていないしコンパイラもサポートしない*1
重要な変更点としては、seqが廃止されている。seqが必要かどうかはコンパイラによって判断される。つまり、型付きschemeとしてだけskySchemeを使いたければ、classの代わりにdefine-packet-formatでpacketを宣言しなければならない*2。。

定義したpacket-formatへのアクセス

graph-IRは、いわゆるSSAなbasic blockの集合で、skySchemeのscheme subsetから生成される。
幾つかのプリミティブによってpacket-formatへアクセスできる。
skySchemeは僕のOSの伝統であるところの、"生成的な名称"を積極的に利用している。つまり、define-packet-typeによって何かdefineすると名前をハイフンでつないだ様々な定義も自動的に生成される。従来は空白を使っていたが、S式的に不都合が多いのでハイフンに代えた。
tcp-headerを例にすると、

  • (compose-tcp-header ((slot obj)(slot obj)...) )

record等と同じように、順序によって名前を付けるタイプでも良いかなと思ったけど、パケットのメンバは非常に多いのが普通なのでこのような形にした。packet-objectを返す。これはクロージャのように扱える。

  • ( slot)

オブジェクトを取り出す。例えば、通常のシチュエーションではtcp-headerはtcp-packetに入っているので、tcp-packetオブジェクトがpだったとすると(p tcp-header)として帰ってきたものはtcp-headerになる。

ホストSchemeとの通信

本来、S式を渡したらパケットになる という方式にしたかったが、パフォーマンスの都合で専用の構文を用いる。これらの構文は、skySchemeによって生成されるR6RSライブラリをインポートして使用する。
というのは嘘で、これらの構文はまだマクロにしていないので、skySchemeが用意したexpanderをまず通して、それからR6RS実装に掛ける方式としている。moshに含めるときにはマクロにしたい。
tcp-headerを例にすると、

  • (define-with-tcp-header name ...)

tcp-headerで宣言されているメンバ名を持った環境で式を実行する。これはskySchemeコード側からのcallbackを作るのに使う。

  • (!tcp-header pid obj)、(?tcp-header pid)

packet-objectを返したりenqueueしたりする。現在のskySchemeはこれらのための型チェックコードを生成しない。
ちなみにこれらの構文はskySchemeのscheme subsetからも扱うことができる(expanderを共用している)。

packetを多義に使いすぎる

skySchemeにとって、packetは文字通りのパケット以外に"環境"としても使われる。クロージャの中の環境は保存されない(!)*3
packetやそのメンバだけがGCの対象であり、packetには任意のオブジェクトを入れることが出来るわけではない(型付けされている)から、Schemeとしての機能は相当に限定されることになる。
さらに、skySchemeは全て接続指向でpacketを取り扱うので、ヒープのサイズはpacketを格納するqueueのサイズとして表現されることになる。

*1:内部的には、u16は(bit-be 16)に変換される等という形で利用されている。型に引数を持たせると型検査のセマンティクスの定義が面倒になる。現状は、名前と引数のeqv?。

*2:直感的でないので修正したい気持ちはある

*3:不便。現状のskySchemeにおけるクロージャはコード断片でしかない。そのうちなんとかしたいが。。