5.1 Cフロントエンド

5.1.1 C言語仕様

言語仕様はISO規格の
   ISO/IEC 9899:1990 Programming Language C
に従っている。これは、1990年版ANSI Cと呼ばれているものと同じである。
ただし、以下のものはサポートしていない。

なお、long double型はdouble型と同じサイズである。 また、前処理とライブラリはgccのものが使われることを仮定している。

5.1.2 プラグマ

概要

 プラグマはコンパイラに対する指示を表すが、その内容を無視しても正しい実行結果が 得られるものであり、最適化や並列化を支援する指示や、プロファイル取得の指示などに 使われる。その形式と内容、意味づけは処理系依存である。プラグマには、人がソースプ ログラム中に記述するものと、コンパイラが後続の処理の仕方を指示するために自動生成 するものがある。

 以下では、ソースプログラムに記入するプラグマをソースプラグマ、 コンパイラが自動生成してHIRに入れるプラグマをHIR-pragma、 LIRに入れるプラグマをLIR-pragmaという。

プラグマの基本形

ソースプラグマ
 Cの場合、ソースプラグマはC言語の仕様にあるように、
    #pragma  item1 item2 ... 
という形の独立の行として表現する。COINSでは、ソースプラグマは次の形の文として扱う。
    プラグマ文 → "#pragma" kindName itemSeq
    kindName → identifier    // 種別を表す識別子。例:prof(プロファイル)
    itemSeq → pragmaItem itemSeq | ε
    pragmaItem → identifier | integerConstant | stringConstant
                | ( itemSeq ) 
pragmaItemの識別子として、プログラムに現れる変数名、副プログラム名、ラベルを書く と、それらは単なる識別子ではなく、変数名、副プログラム名、ラベルとしての識別子と して扱う。

注: ループ並列化やSMP並列化で自動生成するOpenMP向けのプラグマは、OpenMPとして仕様 が標準化されているので、それに従って作る。

HIR-pragma
 HIR-pragmaには、ソースプログラムから変 換されたものと、コマンドのオプション指定などで自動生成されるものがある。 HIR-pragmaはHIRの文(Stmt)としての位置におかれるInfStmtであり、HIRではトーク ンや一般のオブジェクトの位置には置かない。(HIRの一 般のノードにaddInfメソッドで付加しgetInfメソッドで取り出すinf情報は、コンパイ ラに対する指示であってその内容を無視しても正しい実行結果が得られるものであり、プ ラグマと似た扱いのものではあるが、InfStmtのインスタンスでないinf情報はHIR-pragma ではない。)

 前記の形のソースプラグマから作られるHIR-pragmaの一般形は次の通りである。

  Stmt -> ....
    InfStmt
  InfStmt -> ...
      (IR.OP_INF  kindName IrListOfPragmaItems )
             // オペレータがIR.OP_INFの文
  kindName -> String   // プラグマの種類を表す文字列
  IrListOfPragmaItems -> (listCode list_of_pragmaItems)
  pragmaItem ->
       Sym   //  Var, Subp, Label, IntConst, StringConstのノードに限定
     | identifier // Symとは限らない識別子
     | IrListOfPragmaItems // pragmaItemを要素とするIrList 
アクセス方法

 InfStmtの情報はInfStmtのメソッドで次ぎのようにして参照する。ここでinfStmtは InfStmtのインスタンスを表すとする。

   String getInfKind(): kindNameとりだし
   IrList getInfList(kindName): IrListOfPragmaItemsとりだし 
   infStmt.getInfList(infStmt.getInfKind()).get(i):
       IrListOfPragmaItemsの要素iのとりだし 
pragmaItemの識別子として、プログラムの記号表に現れる記号を書いた場合、それは InfStmtではStringではなくSymとして表されている。それをStringとして扱うかSym として扱うかは、個々のプラグマの解釈時に判定し、SymをStringに変換するときはSym のgetName()メソッドを使う。

(注:他のHIRの文とくらべてちょっと変則的なところがあるが、既存の(プラグマでな い)Inf情報との両立性と、プラグマ情報としてはHIRオブジェクトでないSymやString を指定可能とするためにそうなっている。)

LIR-pragma
概要
hir2lir の生成する LIR-pragma
hir2lir が生成する LIR-pragma は、次の 2 種類である。
基本的な変換処理
各要素の対応
hir2lir は入力中の HIR-pragma の要素を次のように変換する。
要素の並び
lir2hir が追加する情報
lir2hir はコンパイル時のオプションなどに基づき以下の情報を追加する。
行番号情報
-coins:debuginfo 指定時に行番号情報を生成する。
(INFO LINE 行番号)
基本ブロック開始情報
基本ブロックの先頭ラベルの直後に以下の INFO 式を付加する。
(INFO (DEFLABEL ラベル名) (LINE 行番号))
基本ブロック終了情報
基本ブロック最後の JUMP の直前に以下の INFO 式を付加する。
(INFO (JUMPx ラベル名))
prologue/epilogue/asm/call 情報
各要素の前後に INFO 式を付加する。
(INFO BEGIN x)
(INFO END x)
x は PROLOGUE, EPILOGUE, ASM, CALL のいずれか。
メモリ参照情報
mem 式に &id で識別子名と参照番号を付加する。対応する識別子が存在しない場合 (ポインタ演算による参照) には後者のように参照番号のみを付加する。
(MEM I32 (FRAME I32 "a.1") &id ("a.1" 0))
(MEM I32 (FRAME I32 "a.1") &id 1)

プラグマの例

ソースプラグマとHIR-pragmaの対応例
以下に、ソースプラグマとそれに対するHIR-pragmaの例をあげる。
#pragma DUMMY
   <inf DUMMY ( )> 
#pragma FUNC fn1
   <inf FUNC ( fn1 )> 
ソースプラグマとLIR-pragmaの対応例
以下に、ソースプラグマとそれに対するLIR-pragmaの例をあげる。
#pragma DUMMY
   (INFO DUMMY)
#pragma FUNC fn1
   (INFO FUNC fn1)

5.1.3 asm文

COINSのCコンパイラでasm文をサポートする。asm文は、アセンブラ言語の 命令列をCプログラムの途中に挿入できるようにするもので、 Cコンパイラが生成する目的コードの中に、asm文で書いた命令列が そのまま挿入される。

5.1.3.1 asm文の仕様

asm文の構文は以下のようなものである。

  asm("#param descriptor-list\n"
      "#clobber destroyed registers...\n"
      "instruction 1\n"
             ...
      "instruction n\n",
      input arguments(any expression)...);

'#param'で始まる行には、パラメタに関する情報を順番に記述する。

パラメタは、レジスタにセットされるもの、static変数のアドレスに分けられる。

レジスタにセットされるパラメタは、入力だけか、出力だけ('w'を付ける)か、入出力('m'を付ける)か、 どんなレジスタにセットされるかを記述する。レジスタは、固定されたレジスタだけでなく、 レジスタクラスの名前を書くこともでき、コンパイラに適切なレジスタを選択させることができる。

'#clobber'には、破壊されるレジスタを記述する。これは固定されたレジスタなので、 作業用レジスタをコンパイラに選択させたい場合には使えない。その場合は、ダミーの変数 をパラメタに書くことになる。

パラメタは、命令部では%1, %2, ...のように引用する。%を出力したいときは%%と二つ続けて書く。

各部の構文は以下の通り。

 descriptor-list:
    descriptor
    descriptor-list , descriptor
 
 descriptor:
    %register
    %register-class-name         /* input parameter, actual parameter is rvalue */
    w%register
    w%register-class-name        /* output parameter, actual parameter is lvalue */
    m%register
    m%register-class-name        /* input and output parameter, actual parameter is lvalue */
    s          /* actual parameter is static address */
 
 register:
    レジスタ名
 
 register-class-name:
    TMD中で定義されたレジスタクラス名(*reg-と最後の*を除く)

例1:

簡単な例であるが、z = x + y; と同じことをasmで行うには 次のように書けばよい。

 asm("#param %I32,%I32,w%I32\n"
     " mov %1,%3\n"
     " add %2,%3\n",
     x, y, z);

例2:

x++; と同じことをasmで行うには次のように書けばよい。

 asm("#param m%I32\n"
     "  add 1,%1\n",
     x);
5.1.3.2 HIRレベルのasm文の仕様
ASM文のHIR表現は
 * AsmStmt    ->        //
 *    ( asmCode attr    // Asm statement.
 *      StringConstNode_ @ //String constant representing formal parameters
 *                       // and sequence of assembly language instructions.
 *      HirList @ )        // List of l-value expressions (variable nodes,
 *                      // pointer expressions, etc.) and arithmetic
 *                      // expressions representing actual parameters.
のように、 とする。

なお、ASM文は文として扱い、算術演算や代入操作の オペランドとしては扱わない。

例:
int x, y, z;
int main()
{
  int a, b, c;
  a = 1;
  b = a + 2;
  z = -1;
  asm("#param %I32, %I32, w%I32\n"
      "mov %1,%3\n"
      "add %2,%3\n",
      a, b+1, z);
  printf("a=%d b=%d z=%d \n", a, b, z);
  return 0;
}
というソースに対するHIRは
 (prog     1
  <null 0 void>
  <nullNode 2>
  (subpDef  3 void
   <subp     4 <SUBP <( )> false false int> main>
  <null 0 void>
   (labeldSt 5 void
    (list 6
     <labelDef 7 _lab1>)
    (block    8 void line 2
     (assign   9 int line 5
      <var      10 int a>
      <const    11 int 1>)
     (assign   12 int line 6
      <var      13 int b>
      (add      14 int
       <var      15 int a>
       <const    16 int 2>))
     (assign   17 int line 7
      <var      18 int z>
      (neg      19 int
       <const    20 int 1>))
     (asm      21 void line 8
      <const    22 <VECT 46 0 char> "#param %I32, %I32, w%I32\nmov %1,%3\nadd %2,%3\n">
      (list 23
       <var      24 int a>
       (add      25 int
        <var      26 int b>
        <const    27 int 1>)
       <var      28 int z>))
     (expStmt  29 int line 12
      (call     30 int
       (addr     31 <PTR <SUBP <( )> false true int>>
        <subp     32 <SUBP <( )> false true int> printf>)
       (list 33
        (decay    34 <PTR char>
         <const    35 <VECT 17 0 char> "a=%d b=%d z=%d n">)
        <var      36 int a>
        <var      37 int b>
        <var      38 int z>)))
     (return   39 int line 13
      <const    40 int 0>)))))
となる。
  asm("#param %I32, m%I32\n"
      "add %1,%2\n",
      i, j);  /* j = j + i; */
に対するHIRは
     (asm      17 void line 11
      <const    18 <VECT 30 0 char> "#param %I32, m%I32\nadd %1,%2\n">
      (list 19
       <var      20 int i>
       <var      21 int j>))
のようになる。

ASM文において、#paramでwというアクセス表示のついた実引数は値が設定され、 mというアクセス表示のついた実引数は値が参照されたあと設定され、 その他の実引数は値が参照されるものとして、データフロー解析される。

5.1.3.3  LIRレベルのasm文の仕様

LIRでのasm文は、ASM命令という専用の命令で表現される。その構文は

(ASM <body-string> <in-list> <out-list> <inout-list>
     &argtype <arg-list>
     &clobber <clobber-list>)

である。<body-string> は、元のbody string (ソースのasm文の本体の中の文字列で表現されている部分)の内容から#paramと#clobber を除き、引数リストの順番をこの構文に合うように書き換えたquoted string (「"」で囲まれた文字列)である。

<in-list>, <out-list>, <inout-list> は、 REG式、STATIC式のいずれかのリストである。 STATIC式は#param で s と指定された引数であり、 それ以外の式は、必ずREG式でなくてはいけない。

inパラメタが複雑な式であれば、REG変数を新しく作り、式の値を事前にその変数に代入し 、inパラメタはREG式に置きかえられる。outパラメタも同様にREG変数を導入し、 ASMの直後にその変数から元のlvalueに代入するコードが追加される。

ただし、いきなりこの制限を満たすことは大変なので、 hir2lirでの変換では任意のL式を許す。

<arg-list> は、#paramの内容を引数リストの順に並べたもの(元の順とは異なる)。

例1:

asm("#param w%I32,%I32,%I32\n"
    "#clobber %eax,%ebx\n"     /* 実際には壊れないが、例として */
    " mov %2,%1\n"
    " add %3,%1\n",
    z, x+1, y);

は、以下のようになる。

(ASM " mov %1,%3\n add %2,%3\n"  ;番号が変わっているのに注意
     ((ADD I32 (MEM I32 (FRAME I32 "x") (INTCONST I32 1)) (MEM I32 (FRAME I32 "y")))  ;in-list
     ((MEM I32 (FRAME I32 "z")))  ;out-list
     ()	      ;inout-list
     &argtype (%I32 %I32 w%I32)  ;wはあってもなくてもいい sは必須
     &clobber (%eax %ebx))    ;clobber

このフォーマットはさらに変換され、最終的に以下のようになる。

(SET I32 (REG I32 ".t1%") (ADD I32 (REG I32 "x%") (INTCONST I32 1)))
(PARALLEL
   (ASM " mov %1,%3\n add %2,%3\n"	;body
        ((REG I32 ".t1%") (REG I32 "y%"))	;in-list
        ((REG I32 "z%"))			;out-list
        () )				;inout-list
        (CLOBBER (REG I32 "%eax") (REG I32 "%ebx")))

このフォーマットでは、レジスタクラスの指定はASM命令からは消え、以下のようにSYMBOL中の変数の宣言部に移される。

("y%" REG I32 4 0 &regset *reg-I32*)

例2:

コンパイル時定数をアセンブラ部分で引用する場合(s型のパラメタ)。

 int vec[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
 int foo(int i) {
   int autov = i;
   int x;

   /* x = vec[i] + autov; */
   asm("#param %I32,s,%I32,w%I32\n"
       " movl %2(,%3,4),%4\n"
       " addl (%1),%4\n",
       &autov, vec, i, x);
   return x;
 }

このasm文は、hir2lir で変換されて以下のようなLIRになる。

 (ASM " mov %2(,%3,4),%4\n add %1(%%ebp),%4\n"
      ((FRAME I32 "autov") (STATIC I32 "vec") (REG I32 "i%"))
      ((REG I32 "x%"))
      ()
      &argtype (a s %I32 w%I32))

  (ASM " movl %2(,%3,4),%4\n addl (%1),%4\n"
      ((FRAME I32 "autov.5") (STATIC I32 "vec") (MEM I32 (FRAME I32 "i.4")))
      ((MEM I32 (FRAME I32 "x.6"))) ()
       &argtype (%I32 s %I32 %I32))

コンパイル後:

  ;i:%ebx, x:%eax, &autov:%edx
  leal -4(%ebp),%edx
  movl vec(,%ecx,4),%eax
  addl (%edx),%eax

5.1.4 Cフロントエンドの使い方

Cフロントエンドは次の形のコマンドによって呼び出すことが出来る。
   java coins.driver.Driver オプション指定  ソースファイル名
「オプション指定」についてはドライバの 「2.2. コンパイラ・ドライバの使い方」 を参照されたい。

「ソースファイル名」は".c"で終わるものでなければならない。

必要なソフトウェア
以下のコマンドが実行出来るようになっていなければならない。

それらが使えず、代わりにものがある場合、たとえば、 アセンブラとして"gas"でなく"as"が使える場合は

   -coins:assembler=as
というオプション指定をすればよい。より詳しくは ドライバの 「COINSオプション」の「その他」の中の
   -coins:preprocessor/assembler/linker
の記述を参照されたい。

printfなどの実行時ライブラリや、その関数定義などのあるヘッダファイル はgccのものを使うことを仮定しているので、それらが必要である。

ただし、引数の個数が不定な関数を実現するためのマクロについては、 x86, sparc, arm, x86_64用のものを

lang/c/include/stdarg.h
として用意してある(armとx86_64用はcoins-1.4.3以降)。

なお、最近のgccでは、ヘッダファイルの中でCOINSではサポートしていない 拡張機能を使っていて、COINSでコンパイルできない場合がある。 その場合は、一つの方法として、より簡単なヘッダファイルを使ってみるとよい。 その例が

lang/c/include/samples/assert.h, string.h
として用意してある(coins-1.4.3以降)。 もう一つの方法は、コンパイルコマンドの行に
 -D_POSIX_C_SOURCE=1 
を入れてみることである。これでコンパイルできるようになる場合がある。
5.1.5 Cフロントエンドの構成

C言語には演算と代入の機能を併せ持つ複合代入演算子などがあるので、そのようなC言語の演算子を そのまま表現できる機能をHIRに追加したHIR-Cを定め、HIR-Cを経由してHIRに変換する方式とした。 また、HIRを作成する際に無駄を省くため、簡単な効率化機能も組み込んだ。したがって、 HIRへの変換過程は次のようになっている。

  1. Cプログラムを抽象構文木ASTに変換
    このモジュールはcoins.cfrontとcoins.astパッケージにある。
  2. ASTをHIR-Cに変換
    このモジュールはcoins.casttohirパッケージにある。
  3. HIR-CをHIRに変換
    このモジュールはcoins.casttohirパッケージにある。
  4. HIRに対する最適化化変換

 Cの前処理系にはgccの前処理系cppを利用する。また、実行時ライブラリもgccのものを利用することにし、 インタフェースをgccに合わせている。

-coins:hirOpt=fromc
というオプション指定をすると、HIR生成時に次のような最適化を行う。
ただし、この指定がなくても、同様な最適化がHIRの基本最適化や バックエンドの最適化で行われるので、生成されるコードに対する効果は あまりないが、生成されるHIRが多少短くなる。
  1. 打ち消しあう演算の削除
       *&v    -->  v
        -(-v)  -->  v、 他
    
  2. 特定の定数に対する演算
       v +  0  -->  v		  v -  0  -->  v
       v *  0  -->  0		  v *  1  -->  v, 他
    
  3. 条件式の効率化
        !(a<b)  -->  a>=b
       定数A 比較演算子 定数B  -->  true または false, 他
       e1 && e2  でe1がtrue  -->  e1の副作用,e2
       r = (a=1)>0 ? 1 : 0;  -->  r = (a=1) , 1;   他
    
  4. 無駄な文の削除
       到達不能の式文/return/goto  -->  削除
       ジャンプ距離が0のgoto       -->  削除
       参照されないラベル          -->  削除
       連続するラベル              -->  ラベルの統合
       空のブロック                -->  削除
       式文                    -->  副作用だけ残して他の部分を削除
    
  5. 条件式が定数(trueまたはfalse)の場合のif文の展開
       if(true) THEN部; else ELSE部; --> { THEN部; goto LABEL_END; ELSE部; LABEL_END:; }, 他
    
  6. 条件式が定数(trueまたはfalse)の場合のループ文の展開
       while(false) BODY部;
    	--> { goto LABEL_BREAK; BODY部; LABEL_BREAK:; }
       for(INIT部;false;STEP部) BODY部;
    	--> { INIT部; goto LABEL_BREAK; BODY部; STEP部; LABEL_BREAK:; }
       do BODY部; while(false);             --> BODY部;
       他
    

詳しくは、COINSのソース・アーカイブのdoc-en/CfrontOpt.txtを参照されたい。

5.2 Fortranフロントエンド

5.2.1 Fortran言語仕様
言語仕様はFortran 77にしたがっている。Fortran 77の規格文書としては以下のものが あるが、内容的にはすべて同じである。

ただし、以下の制限がある。

以下のものはFortran 77の規格の仕様には入っていないが、ベンチマークプログラム などでもよく使われているので、サポートしている。

5.2.2 Fortranフロントエンドの使い方
Fortranフロントエンドは次の形のコマンドによって呼び出すことが出来る。
   java coins.driver.F77Driver オプション指定  ソースファイル名
「オプション指定」についてはドライバの 「2.2 コンパイラ・ドライバの使い方」 を参照されたい。

「ソースファイル名」は".f"で終わるものでなければならない。ソースファイル名は "foo.f bar.f ..."のようにいくつか並べても良い。

なお、

   -coins:printhir
というオプションを指定すると、FortranプログラムをHIRに変換した後で、HIRの木と シンボルテーブルを出力する。

これは、coins.driver.F77Driverの中に、次のような記述があるからである。 このようにして、任意のオプションを追加することが出来る。

   if (io.getCompileSpecification().getCoinsOptions().isSet("printhir")) {
      hirRoot.programRoot.print(0);
      io.printOut.print("¥n");
      symRoot.symTable.printSymTableAllDetail(symRoot.symTableRoot);
      symRoot.symTableConst.printSymTableDetail();
      io.printOut.print("¥n");
    }
必要なソフトウェア
Cで必要なソフトウェアのうち、プレプロセサ、アセンブラ、リンカはFortranでも必要である。

その他に、実行時ライブラリとして"libf2c"が必要である。このライブラリは http://www.netlib.org/f2c/libf2c.zip をダウンロードして、その中にあるREADMEにしたがってインストールすれば良い。

UNIX系のOSの場合、解凍したlibf2cのディレクトリで

% cp makefile.u Makefile
% make
を実行して得られる"libf2c.a"を適当なところにおいて、その置き場所を Fortran Driverに認識させれば良い。そのためには
libf2cLocation	  /directory/path/where/libf2c.a/exists
のように、"libf2c.a"の置き場所を指定する行を含んだファイルを"settings"という名前で ドライバの 「2.3.6  ライブラリ・ディレクトリ」に書かれているところに置けば良い。

ライブラリ・ディレクトリは、特に指定しない場合は、

~/coins/

となっているので、そこに"settings"ファイルを置けば良い。

なお、cygwinの場合は、上記の手続きに少し修正が必要である。まず、上記のMakefileの中にある"a.out" を"a.exe"に変更する必要がある(2ヶ所)。また、cygwinでは、上記のライブラリ・ディレクトリを認識せず、 現在作業中のディレクトリを探すようであるので、"settings"ファイルは作業中のディレクトリに置いておく必要がある。

"libf2c.a"を使わなくても,linkerとしてgccの代わりにg77を使えばFortranプログラムのコンパイルと実行が可能になる. その場合は,上記の"settings"ファイルを取り除き,coins.driver.F77DriverのsetDefaultLinkerOptionsメソッドの 中の

 options.add("-lf2c");

を取り除く必要がある.コンパイルするコマンドは,たとえば次のようにすればよい.

$ java -classpath coinsディレクトリ/classes coins.driver.F77Driver \
    -coins:assembler=as,linker=g77,target=x86 reidai1.f
5.2.3 Fortranフロントエンドの構成
coins.driver.F77Driverはcoins.driver.Driverのサブクラスであり、 フロントエンド呼び出しのメソッドをオーバライドしているだけである。 したがって、coins.driver.Driverのほとんどの機能はそのまま使うことが出来る。

Fortran言語には、ENTRY文、assigned GOTO文など、最近の言語にはない ものとしてHIRには用意されてない機能がいくつかある。それらについては プログラム変換をしている。おおまかには、それと等値な働きをするCプログラムを 考えて、それのHIRに相当するものに変換している。

HIRへの変換過程は次のようになっている。

  1. FortranプログラムをFortran用中間表現Firに変換
  2. FirをHIRに変換

これらのモジュールはcoins.ffrontパッケージにある。そのディレクトリ内にある

によって字句解析と構文解析が行われる。

詳しくは Fortran特有の機能に対する考慮Fortranコンパイラの設計、 を参照されたい。

5.3 新しいフロントエンドの作り方

COINSで新しいフロントエンドを作った例としては、C0コンパイラ、PL0コンパイラ、Simpleコンパイラ、 Fortranコンパイラなどがある。
最初のC0コンパイラについては、情報処理学会誌のVol.47のNo.4とNo.5に連載された 「21世紀のコンパイラ道しるべ ・・COINSをベースにして」に説明がある。 後ろの3つのコンパイラについては、それらの説明が下記のページにあるので、 それを参照されたい。 http://coins-compiler.sourceforge.jp/advanceduse/compilerDevelop.html

5.3.1 JavaCC(LLパーサ生成系)を使った例

C0コンパイラのフロントエンドのJavaCCによる記述は http://coins-compiler.sourceforge.jp/IPSJ-mitisirube/ のc0frontを参照されたい。

PL0コンパイラのフロントエンドのJavaCCによる記述は http://coins-compiler.sourceforge.jp/advanceduse/ll/coinsCompilerPL0.html を参照されたい。

5.3.2 LRパーサ生成系を使った例

C0コンパイラのフロントエンドのnotavaccによる記述は http://coins-compiler.sourceforge.jp/IPSJ-mitisirube/ のc0front2を参照されたい。

Simpleコンパイラのフロントエンドのjayによる記述は http://coins-compiler.sourceforge.jp/advanceduse/simple/coinsCompiler.html を参照されたい。