Let's start Scheme

2014-03-03

続 コンパイラのバグ

一つ前の投稿でコンパイラのバグについて書いた。週末を利用して先に展開するのを試してみたのだが見事に穴にはまったので記録しておく。

問題になったのは、マクロの展開とコンパイラの環境が密な関係にあることである。マクロ展開時にはコンパイラが集めた環境フレームを利用しているのだが、局所マクロを先に展開してしまうとそれを当てにした変数参照が動かなくなる。端的なコードしては以下のものがだめになる。
(define (bar)
  (let-syntax ((foo (syntax-rules () ((_ b) (when (< b 10) (bar))))))
    (define (buz b) (foo b))
    (buz 10)))
これが展開後には以下のようになる。
(define (bar)
  (define (buz b) (when (< #<id b> 10) (bar)))
  (buz 10))
問題になるのは識別子#<id b>で現在のマクロ展開器ならば識別子が持つ環境フレームに変数bが入って変数参照手続きがたどれるようになっている。しかし、ナイーブな実装で先にマクロだけを展開してしまうと生成された識別子は局所変数の参照を持たないフレームを持つことになり変数参照がうまくいかない。

これを解決するとすれば以下の2通りだろう。
  1. 識別子が持つフレームが参照する変数を含んでいない場合には共有している環境以前のみを探してみつける
  2. マクロ展開器が変数束縛を検知する
1は無駄に複雑になるだけなのでやるつもりはない。複雑なコードは理解を阻害しバグを混入させるだけなのだ。(既に複雑怪奇になっていて自分でも全てのパターンを列挙できないようになっている・・・orz)
2は環境フレームの同値性に頼っているコードが山ほどあるので事実上不可能。やれなくはないが、複雑怪奇に(ry
となるとこの方向性で解決するには、全てのマクロをあらかじめ展開してしまうというR6RSが要求している方針を採らざるを得なくなる。マクロの展開とコンパイルを同時にやるというのは不可能ではない(はずな)のだが、現状のコンパイラは中間表現にGauche由来のIFormを使っているためS式との混在がきつい。

となるともう一つの方法である現状のコードを拡張する方向だが、これはこれでまたバグの温床になりそうな雰囲気が既に漂っているのでうれしくない。ちょっと方針に以下の項目も入れる方向で検討することにする。
  • IFormを捨てる
    • 大幅なコンパイラの変更が必要
  • マクロ展開フェーズを設ける
    • 重複コードをどうするか?
場当たり的な対応ではバグを埋め込むだけなので根本から解決する必要がありそうではある。

No comments:

Post a Comment