Fortran特有の機能に対する考慮


 coinsの目標は,いろんな言語に共通に使えるHIRを設計するということだから,Fortranの機能で現在のHIRで素直に表現できないものは,素直に表現できるようにHIRを変更すべきであるが,それがあまりにFortran特有の機能であって,現在の他の普通のプログラム言語では考えられないものであるのならば,特有の表現を通常の表現に変換して済ませるほうがよい.
 また、HIRは一つの言語であり、その言語のプログラムに対して、種々の最適化やLIRへの変換が実現されているのであるから、HIRに新たな機能を追加するときは、それをあまり乱さない考慮が必要である。
 Cについては,現在はソースプログラムをまずHIR-Cに変換し,次に,HIR(HIR-Cに対比するときはHIR-Baseと呼んでいる)に変換している.C特有の機能で,HIR上での処理が複雑になるようなものは変換してしまって,HIRを単純で標準的な形に保つためである.そこでFortranでも,最初はHIR-Fortranと呼ぶようなものを考えようとした。しかし、HIR-FortranでFortranのプログラムを出来るだけ忠実に表現しようとすれば、それはもはやHIRとは言えず、Fortran固有の表現になってしまう。次に、現在のHIRで直接表現できないようなものはいったんHIR-Fortranとして表現しておいて、それからHIR-Baseに落とすことを考えた。しかし、それは実現上の手段に過ぎず、HIR-Fortranとして独立した意味を持たない。そこで、以下ではHIR-Fortranという言葉は使わないことにした。
 しかし,Fortranはきれいに設計された言語ではないので,単純にシーケンシャルにHIRに変換することは出来ない.そこで,まず抽象構文木に変換することにした.ただし,一つの根からなる木ではなく,宣言情報やENTRY文などは別の木にして,全体としてはそれらの林になるものとした.

 Fortranには,77,90,95があるが,90以降は仕様が大変大きくて,実現は困難であり,実際にもあまり使われていないようなので,ここではFortran 77を対象として考えることする.
>
 FortranでCと違っている、あるいは現在のHIRに直接入っていない主な点と,coinsを使って実現する場合の案を以下に記す.
 基本的には,現在のHIRに変更は加えず,それに合わせたHIR木を生成することにした.Fortran特有の機能には標準的な言語の機能と考えられないものが多いからである.COMPLEX型は標準的な機能としてHIRに追加することも考えられるが,コードの最適化のことを考慮すると,COMPLEX型としての表現よりも,それを実数型に分解した表現の方が最適化の機会が多くなる.したがって,COMPLEX型をHIRに追加することはせず,それは,実数部と虚数部を持ったstruct型として表現することにした.
 HIRは当初から,少なくともCのコンパイラが作れること,という目標で仕様が作られているから,Cで表現できるものはHIRで表現できる.そこで,以下では,Fortran特有の機能をどのようなCの表現に変換するかの案を示す.実際にはそのようなC表現そのものを作るのでなく,それに相当するHIR表現を作る.その変換の途中で,都合によりHIRの持つクラスのサブクラスとして,Fortran特有の機能を表現するためのサブクラスを一時的に作ることはあるが,でき上がったHIR表現にはそのようなサブクラスのオブジェクトは入っていない.
 なお字句解析のプログラムlexは手書きで,構文解析のプログラムはBerkeley yaccのjava版であるjayを使うことを考えている.

以下で参考にしているf2cの内容はベル研のレポート

  Computing Science Technical Report No. 149
  A Fortran-to-C Converter
  S. I. Feldman, D. M. Gay, M. W. Maimone, N. L. Schryer
  Last updated March 22, 1995. Originally issued May 16, 1990.

による.

以下に、Fortran特有の機能とそれに対する対策を述べる。

(1)字句解析が難しい
 72欄のカード形式,スペースの有無が自由,キーワードが無い,変数は宣言しなくてもいい,関数呼出しと配列要素が同じ形,など字句解析を難しくする仕様がある.
 これについては,手書きの字句解析プログラムで,構文解析しやすいトークン列に変換する.OMNIのFortranフロントでもそのようにしており,それを参考にする.

 字句解析では以下の処理をする.
 ・継続行はまとめて1つの文とする.
 ・文の分類をする(代入文,do文,FORMAT文など)
   ただし,代入文と文関数定義文の区別は出来ない(その区別は構文解析時).
 ・代入文と文関数定義文にはletというキーワードがついていることにする.
 ・各文の最後にはend_of_statementトークンがついていることにする.
 ・各文の先頭には「ラベルなし」トークンか「ラベル定義」トークンかがついている
   これはOMNIにはない.

(2)ENTRY 文
 サブプログラムが入口を複数もつ.例えば,次のプログラム

    SUBROUTINE SUB1(X, Y)
     ...
  10 ENTRY SUB2(X, Z)
     ...
    END
 は
    SUBROUTINE SUB1(X, Y)
    CALL SUB12(1, X, Y, dummy)
    END
  
    SUBROUTINE SUB2(X, Z)
    CALL SUB12(2, X, dummy, Z)
    END
  
    SUBROUTINE SUB1_(J_, X, Y, Z)
    IF ( J_ .EQ. 2 ) GO TO 10
     ...
  10
     ...
    END

 に変換する.実際には次のようなものに変換することにした.

    void SUB1(float *X, float *Y){
      int i_ = 1; float dummy_;
      SUB12(&i_, X, Y, &dummy_);
    }

    void SUB2(float *X, float *Z){
      int i_ = 2; float dummy_;
      SUB12(&i_, X, &dummy_, Z);
    }

    void SUB1_(int *J_, float *X, float *Y, float *Z){
      switch(*J_) {
      case 1: goto L_SUB1;
      case 2: goto L_SUB2;
   L_SUB1:
      ...
   L_SUB2:
      ...
    }

 FUNCTIONの場合はもう少し複雑になる。

    FUNCTION f(r)
    f = r
    ENTRY g(s)
    g = f + s
    END

 は

    float f(float *r){
     float _f, dummy_;
     int i_ = 1;
     _f = f_(&i_, r, &dummy_);
     return _f;
    }

    float g(float *s){
     float _g, dummy_;
     int i_ = 2;
     _g = f_(&i_, &dummy_, s);
     return _g;
    }

    float f_(int *i_, float *r, float *s){
     float _f_;
     switch(*i_){
     case 1: goto L_f;
     case 2: goto L_g;
     }
    L_f: _f_ = *r;
    L_g: _f_ = _f_ + *s;
     return _f_;
    }

に変換する.実は,Fortranの規格では,fとgの型が違ってもよいことになっているが,それを実現するのは簡単ではない.f2cでも考えていないようなので(確認はしていません),それは考えないことにする.実現するとしたら,次のようにすることが考えられる.

    FUNCTION f(r)
    INTEGER f;
    f = r
    ENTRY g(s)
    g = f + s
    END

 は

    int f(float *r){
     int _f, dummy_;
     int i_ = 1;
     _f = (int)f_(&i_, r, &dummy_);
     return _f;
    }

    float g(float *s){
     float _g, dummy_;
     int i_ = 2;
     _g = (float)f_(&i_, &dummy_, s);
     return _g;
    }

    union{int, float} f_(int *i_, float *r, float *s){
     float float_; int int_;
     switch(*i_){
     case 1: goto L_f;
     case 2: goto L_g;
     }
    L_f: int_ = *r;
    L_g: float_ = int_ + *s;
     switch(*i){
     case 1: return int_;
     case 2: return float_;
     }
    }

 に変換する.

(3)CALL 文のalternate return specifier
 引数で指定された文番号のところにreturnすることが出来る.たとえば,次のようなプログラムが書ける.

  CALL SUB(A, *100, *200)   SUBROUTINE SUB(X, *, *)
                  RETURN 2
                  (ステートメント200にリターン)

 これは,次のようなプログラムに変換する.

  GO TO (100, 200) ,SUB(A)    INTEGER FUNCTION SUB(X)
                   SUB = 2
                   RETURN

 Cの表現では,次のようになる.

  switch(SUB(A)){        int SUB(X) {
  case 1: goto L_100;        return 2;
  case 2: goto L_200; 
  }                }

(4)statement function
 1つの文で関数を定義することが出来る.それを1つのサブプログラムとすることも出来るが,ここでは,文関数呼出しはインライン展開することにする.

(5)ASSIGN文
   ASSIGN s TO i   sは文番号,iは整数変数
 は
   i = s
 に変換する.これは,通常の整数の代入文である.次の(6)と合わせて機能する.

(6)assigned GO TO 文
   GO TO i [ (s [,s]...)]    sは文番号,iは整数変数
 は
   switch(i){
   case s: goto L_s   (最初のsは整数定数.後ろのL_sはラベル)
   . . .
   }
 に変換する.

(7)computed GO TO 文
   GO TO (s [,s]...) i    sは文番号,iは整数変数
 は
   switch(i){
   case 1: goto L_s1
   case 2: goto L_s2
   . . .
   }
 に変換する.

(8)算術IF文
   IF (e) s1, s2, s3
 は
   if (e < 0) goto L_s1
   if (e == 0) goto L_s2
   else goto L_s3
 に変換する.

(9)COMPLEX型
 複素数型はstruct型として表現することにする.複素数型に関する演算は実数型の演算に分解する.たとえば,
   COMPLEX c, d
   c = c + d
 は
   struct complex { float re; float im } c, d;
   c.re = c.re+ d.re;
   c.im = c.im + d.im;
 に変換する.

(10)expressionの評価
 ある部分を評価しなくてもexpressionの値が決まる場合,その部分を評価しなくてもよい.評価の順序は,mathematically equivalent expressionに変えて評価してよい.ただし,カッコで囲まれたexpressionは一つのentityとして扱わねばならない.
 HIRには,Fortranのこの規則を考慮してカッコで囲まれたexpressionであることを表すencloseという演算子が用意されているから,それを使うことにする.

(11)**演算子
 べき乗演算子をHIRに追加することはしない.べき乗演算子は,一般には関数呼出しに変換する.いくつかの定数整数べき乗は掛け算にインライン展開することにする.

(12)COMMON
 COMMONブロックはstructで表現する.

(13)EQIUVALENCE
 EQIUVALENCEはunionで表現する.

(14)多次元配列のメモリ配置
 たとえば,
   DIMENSION k(10, 30)
   ...k(3, 7)
 は
   int k[30][10];
   ... k[6][2]
 のSymとHIRに相当するものに変換する.

(15)SAVE文
   SAVE [a [,a]...]
 サブルーチンからリターンしても,ここにリストした変数の値は保持される.そのために,SAVE文にある変数をstaticとする.

(16)引数について
 call by addressであるから(call by valueも許す処理系はあるが,規格(http://www.fortran.com/fortran/にある)には入っていない),引数はポインタ型に変換する.実引数が変数以外の式である場合はダミーの変数への代入文を作り,その変数のアドレスを実引数とする.

(17)入出力文とFORMAT文
 open,close,backspace,rewindはライブラリ呼出しの形とし,FORMATは文字列の引数として入出力のライブラリ(f2cのlibF77とlibI77)を呼び出す形とする.入出力のリストとしてのループはループ文に展開する.

(18)配列の宣言
 配列の宣言が,どこからでもいい a(-10:10,-1:10) b(100:105).サブプログラムでは仮引数の配列としてadjustable array ,assumed-size array(最後の上限が*)などが使える.
 Fortranでの配列の宣言の情報(仮引数などを含んだ式による上下限の指定など)をすべて扱えるようにHIRのVector型を拡張する.

(19)文字列と部分文字列
 文字列の宣言でcharacter c*500,d*100という書き方をすると,cは長さ500の文字列型である.文字列型を関数の戻り型とすることもできる.
 代入文
   c(1:200)=c(2:201)
 で部分文字列の代入が出来る.

(20)data文やblock data文
 部分文字列やimplied-DOリストにも初期値設定できる.初期値としてn*cの形でn個の定数を表現できる.

(21)implicit 宣言,ijklmn規則
 宣言なしでも変数の型が決まる.

(22)PARAMETER文
 定数名の宣言

(23)PAUSE文,STOP文
 ライブラリ呼出しにする.PAUSEのデフォルトは何もしないライブラリ.STOPはプログラム終了.

(24)記憶単位
  INTEGER,REAL,LOGICALは1数値記憶単位(numeric storage unit)を占める
  DOUBLE PRECISIONは2数値記憶単位を占める
  COMPLEXは2数値記憶単位を占める(最初の1単位が実数部,次が虚数部)
  CHARACTER(1文字)は1文字記憶単位を占める

  INTEGERはHIRのint(LIRのI32)とする
  REALはHIRのHIRのfloat(LIRのF32)とする
  DOUBLE PRECISIONはHIRのdouble(LIRのF64)とする
  COMPLEXは2つのfloatとする
  CHARACTER(1文字)はHIRのchar(LIRのI8)とする
  LOGICALはHIRのbool(LIRのI32)とする

(25)文字列定数
 Cでは,"abc"は長さ4の文字配列.Fortranでは'abc'は長さ3