半年くらいブログをほったらかしにしていました。おひさしぶりです。 ついったーにC++のコードを張り付けていても検索性が低いなぁと思ったので今後はブログにまとめていこうかなぁという感じです。
ISAシミュレータを作るとき、一つ一つの命令の定義は非常に単純なのに、付加的な情報を定義しなければいけないために冗長になってしまうことがよくあります。
たとえば、レジスタの値をストアするSTORE
命令を考えてみると、
mem[src[1]+imm[0]] = src[0];
くらいの情報量で充分に定義できているはずです。 しかし、ソースレジスタが2個だとか、デスティネーションレジスタはないだとか、そういった情報も使いたくなることがあります。 そういった情報は先ほどの定義から原理的には抽出できるはずですが、C++コードで行う方法は必ずしも自明ではありません。 また、ISAシミュレータは速いことが望まれますので、出来ることなら実行時に動的に判断するのではなく、コンパイル時に静的に決まってほしいです。 こういった情報を別の場所に手書きするのは、保守性が低下するのであまりやりたくないところです。
テンプレートメタプログラミングを使うとそういったこともできるということを示すためにちょっと書いてみました。
ltmpcほどテンプレートメタプログラミングという感じではなく、constexpr
要素も含んでいます。
各命令の動作の定義には、ジェネリックラムダを使います。auto
型の引数の部分は実質テンプレートなので、あえて変な型のオブジェクトを入れることもできます。
ここで、入れることのできる変な型のオブジェクトは、単に同じ名前の変数や関数が定義されていれば問題なく動きます(いわゆるダックタイピング的なことができます)。
これを利用(悪用ですね)して、ラムダ式の中でどのような変数にアクセスしているかを検出するオブジェクトを作ることができます。 これをラムダ式の引数に与えることで、ラムダ式の中での動作を調べ上げることができます。
今回は単にアクセスされるかだけの判定でしたが、演算子オーバーロード・式テンプレートと組み合わせればどういった依存関係になっているのかということも判定できそうです。
最初から式テンプレートを使えばconstexpr
なしの完全TMPで同じことができそうに思えますが、src[0]
みたいな部分はうまく扱えないです。0_src
とかsrc<0>()
とか書けば型に落とし込めますが、見た目とのトレードオフになりそうです。有限個なのでとあきらめてsrc0
みたいな変数を作ってしまうのはちょっと負けた感じがあります。
ただ、こういうのを書くのは、簡単な命令の時は有効ですが、わけのわからない細かい仕様のある命令の時は結局愚直に書く必要があって、あまり楽をできなくなってしまいます。悲しい。