Exc compiler tool kit in Java

概要

exc toolsは、C/C++やFortranのコードを解析したり、コード変換、最適化を するためのプログラム群である。C/C++やFortranのソースコードは、フロント エンドプログラムによって、Xobject codeという中間コードに変換する。 現在、C言語のフロントエンドがあり、FortranとC++を準備する予定である。 このXobject codeは、Cプログラムに復元可能なように設計された中間コード である。Xobject codeは、javaによるパッケージを使って、コード変換するこ とができる。現在、次のようなパッケージが用意されている: 以下にexc toolsを用いたプログラムの構成について、示す。
現在、flow解析などのパッケージをimplement中である。

Xobject File とは

Xcode は、C言語プログラムに復元可能な中間コードである。このコードレベ ルでは、以下の特徴を持つ:

C-frontは、CのソースプログラムをXobject codeに変換する。 C-frontでは、OpenMPのdirectiveを認識できるようになっており、OpenMPのコ ンパイラのフロントエンドとして用いることができる。

Xobject Fileは、タイプ情報、大域変数のシンボルテーブル、プログラムの定 義からなっている。プログラムは、tree構造で表現されている。 例えば、以下のソースプログラムは、

main()
{
    int i,s;
    s = 0;
    for(i = 0; i < 10; i++) s+= i;
    printf("sum = %d\n",i);
}
次のようなXobject Fileに変換される。
# タイプ情報
{F8066080 0x0 0x0 0 0 int 0 ()}
{P80660b8 0x4 0x4 0 0 F8066080}
{F8065560 0x0 0x0 0 0 int 0 ()}
{P8066740 0x4 0x4 0 0 F8065560}
{P8066150 0x4 0x4 0 0 int}
{P8066200 0x4 0x4 0 0 int}
{P8066778 0x4 0x4 0 0 F8065560}
{P8065520 0x4 0x4 0 0 char}
{P80667f0 0x4 0x4 0 0 int}
%
# 大域シンボルテーブル
 [main extern_def F8066080 (FUNC_ADDR:P80660b8 main)]
 [printf extern F8065560 (FUNC_ADDR:P8066740 printf)]
%
# プログラム定義
(FUNCTION_DEFINITION (IDENT main) (ID_LIST) (LIST)
  (COMPOUND_STATEMENT
    (ID_LIST # ローカルシンボルテーブル
             [i auto int (LVAR_ADDR:P8066150 i)] 
             [s auto int (LVAR_ADDR:P8066200 s)])
    (LIST (VAR_DECL (IDENT i) ()) (VAR_DECL (IDENT s) ()))
    (LIST
      (EXPR_STATEMENT(ASSIGN_EXPR:int (LVAR:int s) (INT_CONSTANT:int 0)))
      (FOR_STATEMENT
        (ASSIGN_EXPR:int (LVAR:int i) (INT_CONSTANT:int 0))
        (LOG_LT_EXPR:int (LVAR:int i) (INT_CONSTANT:int a))
        (POST_INCR_EXPR:int (LVAR:int i))
        (EXPR_STATEMENT
          (ASG_PLUS_EXPR:int (LVAR:int s) (LVAR:int  i))))
      (EXPR_STATEMENT
        (FUNCTION_CALL:int (FUNC_ADDR:P8066778 printf)
          (LIST
            (STRING_CONSTANT:P8065520 "sum = %d\n")
            (VAR:int i)))))))
プログラムはいわゆるAbstract Syntax Tree(AST)で表現されている。

Xobject package

Xobjectパッケージは、Xobject Fileをjavaで扱うためのパッケージである。

クラス XobjectFileは、Xobject File全体を扱うためのクラスである。 foo.xという名前のXobject Fileを読み込み、goo.xに書き込む処理を行う場合 には、以下のようにする。

XobjectFile f = new XobjectFile("foo.x","goo.x");
f.Input();
何らかの処理...
f.Ouptut();
または、
XobjectFile f = new XobjectFile();
f.Input("foo.x");
何らかの処理...
f.Ouptut("goo.x");
としてもよい。コード変換した結果を、Cコードにする場合には、Decompile methodを用いる。
f.Decompile("goo.c");
ファイル内の関数定義は、XobjectDefというクラスで表現されている。 Xobject内の関数定義について、解析やコード変換をするためにはXobjectFile のiterateDef methodを用いる。このメソッドは、関数定義について、 XobjectDefVisitorインタフェースを持つオブジェクトのdoDef(XobjectDef) methodを呼び出す。
class MyFuncVistor implements XobjectDefVisitor {
   public void doDef(XobjectDef d){ 
       関数定義 d についての処理 ...
   }
   ...
}


XobjectVisitor op = new MyFuncVisitor;
f.iterateFuncDef(op);
例えば、関数定義の名前をプリントアウトする例を考えて見よう。
class PrintFuncName implements XobjectDefVisitor {
   public void doDef(XobjectDef d){
	System.out.println("Func name is "+d.getName());
   }
   public static void main(String args[]){
       XobjectFile f = new XobjectFile();
       f.Input("foo.x");
       XobjectVisitor op = new PrintFuncName();
       f.iterateFuncDef(op);
  }
}
XobjectDefの関数定義名を得るメソッドは、getName()である。

さて、このパッケージの重要なクラスは、tree構造を表現するXobjectクラスである。 関数の定義の本体はこのクラスで表現されている。 このクラスはアブストラクトクラスとして用いられ、実際は以下のサブクラス で表現する:

Xobjectは、演算子や文の種類を示すコード(Opcodeと呼ぶ)とXobjectの表現す る式のデータ型を示すタイプを持つ。Opcodeは、Xcodeクラスにintegerの定数として、 定義されており、各XobjectのOpcode() methodでアクセスする。 XobjList以外のXobjectは、treeの終端ノードであり、isTerminal() methodを 調べることができる。各サブクラスごとに、getInt()やgetName() などの methodを用いて、整数値や文字列の引数にアクセスすることができる。

リストを表現するXobjListの場合には、その引数になっているn番目Xobjectに アクセスするためには、getArg(int)を用いる。 例えば、始めの引数にアクセスするためには、getArg(0)でアクセスする。 binaryの演算子の場合(isBinaryOp())は、引数が2つであり、left(),right() methodを使ってもよい。 unaryの演算子の場合(isBinaryOp())は、引数が1つであり、operand() method を使ってもよい。left(), operand(), getArg(0)は同値の操作である。 引数を順番にアクセスするためには、引数へのイタレータXobjArgsを使って行 う。イタレータは、getArgs() methodで得る。

 for(XobjArgs a = v.getArgs(); a != null; a=a.nextArgs())
	x = a.getArg();  // xは引数。
基本的には、Xobjectのopcode、またはinstanceofにより、分岐して、Xobject 内のノードをトラバースできる。例えば、
void traverse(Xobject x){
   .... ; // なんらかの操作 
   if(x instanceof XobjList){
         for(XobjArgs a = v.getArgs(); a != null; a=a.nextArgs())
	         traverse(a.getArg());
   }
}		 
XobjectIteratorは、Xobject内の全てのXobjectをトラバースするためのイタ レータであり、アブストラクトクラスである。サブクラスとして、 bottomupXobjectIteratorとtopdownXobjectIterator の2つがあり、トラバー スする順番により選択する。例えば、上の例では、
XobjectIterator i = new topdownXobjectIterator(x);
for(i.init(); !i.end(); i.next(){
     x = i.getXobject();
     ...; // なんらかの操作 
}
と記述することができる。

プログラム中の全ての変数の参照をプリントアウトする例を示す。

class PrintVarRef implements XobjectDefVisitor {
   public void doDef(XobjectDef d){
        String fname = d.getName();
	XobjectIterator i = new topdownXobjectIterator(d.getFunBody());
	for(i.init();!i.end(); i.next()){
            Xobject x = i.getXobject();
            if(x.isVariable() || x.isVarAddr())
	        System.out.println("Variable '"+v.getName()+
		"' is referenced from Function '"+fname+"'");
       }
   }
   public static void main(String args[]){
       XobjectFile f = new XobjectFile();
       f.Input("foo.x");
       XobjectVisitor op = new PrintVarName();
       f.iterateFuncDef(op);
  }
}

データ型は、Xtypeクラスで表現されている。Xobjectのデータ型は、Type() methodでアクセスすることができる。 Xtypeは、データ型を表現するアブストラクトクラスで、実際にはそれぞれの データに対応して以下のサブクラスで表現される:

Xtypeがどのようなデータ型であるかは、XtypeクラスのgetKind()をから返さ れる値、あるいはinstanceof演算子で調べることができる。Xtypeクラスと XobjectクラスのisPointer(), isFunction() などのmethodを使ってもよい。

Xobject中のシンボルテーブルは、Xcode.ID_LISTというOpcodeを持つリストと して表現されている。このリストは、IdentクラスのXobjectを引数とするもの で、ファイルスコープの大域シンボルテーブルはXobjectFileクラスの getGlobalIdentList() で得ることができる。 関数中の局所変数については、Xcode.COMPOUND_STATEMENTのリストの第1引数 がシンボルテーブルである。このリストは、他のリストと同様に、XobjArgsを 用いて、それぞれのシンボルテーブルのエントリにアクセスする。 Identクラスはシンボルテーブルエントリを表すもので、getName()でシンボル 名、getStorageClass()で記憶クラスの値(StorageClassクラスに定義されてい る)、Type() でデータ型を得ることができる。

入出力は、基本的にはXojectFileのInput()、Output()で行うが、デバックな どのために、Xobjectの入出力には XobjectInputStreamクラス、 XobjectOutputStreamクラスを用いることができる。

それぞれのXobjectは、new オペレータを用いて、生成することができるが、 表現が冗長になったり、生成時にデータ型を指定しなくてはならなかったり、 面倒なことが多い。そのため、static メンバ関数として、コンストラクタの ための関数を用意したクラスがある。 XcosはXobjectのためのコンストラクタのクラスである。例えば、

Xobject x = new XobjInt(BasicType.intType,100);
は、Xconsクラスを用いて、以下のようにすることができる。
Xobject x = Xcons.Int(BasicType.intType,100);
類似のmethodは、XobjIntだけではなく、XobjString, XobjListにも定義され ている。例えば、c = a+bを表現するXobjectを作るには、
Xobject x = Xcons.List(Xcode.ASSIGN_EXPR,null,
                        Xcons.Symbol(Xcode.VAR,BasicType.intType,"c"),
                        Xcons.List(Xcode.PLUS_EXPR,BasicType.intType,
                             Xcons.Symbol(Xcode.VAR,BasicType.intType,"a"),
                             Xcons.Symbol(Xcode.VAR,BasicType.intType,"b"));
更に、よく使われる演算子にはコンストラクタのためのメソッドが定義されて いる。たとえば、上の例では、
Xobject x = Xcons.Set(Xcons.Symbol(Xcode.VAR,BasicType.intType,"c"),
                Xcons.binaryOp(Xcode.PLUS_EXPR,
                        Xcons.Symbol(Xcode.VAR,BasicType.intType,"a"),
                        Xcons.Symbol(Xcode.VAR,BasicType.intType,"b"));
としてもよい。Xcons.binaryOp()は引数にしたがって、タイプを決定したり、 constant foldingする(まだ、未実装)。

Xtypeには、コンストラクタのためのstaticな関数が定義されている。 例えば、整数型に対するポインタを作るためには、

Xtype t = new PointerType(BasicType.intType);
Xtype t = Xtype.Pointer(BasicType.intType);
としてもよい。関数型、配列型にも同様なコンストラクタのための関数がある。

Xblock package

Blockパッケージは、プログラム中の文の構造を認識し、解析したり、コード 変換をするためのパッケージである。Xobjectクラスでは、tree構造をトラバー スしたり、簡単な置き換えはできるが、前後の参照関係を調べたり、文を挿入 や削除したりすることは難しい。 基本となるデータ構造は、Blockである。以下のような構造をしている。
Block.gif
各Blockは、Basic Blockを持つことができる。Basic Blockは分岐を持たない 複数の文と条件式を表現する。

ネストする構造を持つ場合には、そのbody部を表現するために、 BlockListを持つことができる。BlockList はbody部となる複数のBlockからな る列を表現する。

BlockList.gif
BlockListは、Compound statementに対応する構造も表現しており、 シンボルテーブルを格納している。

関数定義XobjectDefやXobjectからBlockへは、 Bcons.buildFunctionBlockまたは、Bcons.buildBlockを用いて変換する。クラ スBconsは、コンストラクタを集めたstaticなmember関数のクラスで、文に対 応するBlock構造を作ることができる。 Xobjectへは、Blockのメンバ関数toXobjectをつかって、コード変換した結果 を戻すことができる。 例えば、各関数について、解析コード変換を行うXobjectDefVisitorインタフェー スを持つプログラムのdoDefは以下のようになる。

   public doDef(XobjectDef d){ 
       	Block fblock = Bcons.buildFunctionBlock(d);
	... ; // fblockに対して解析、コード変換を行う。
	d.setDef(fblock.toXobject());
   }
Blockクラスは、ブロックを表現するアブストラクトクラスであり、実際には 各文の構造に対して、以下のサブクラスを用いる。

Blockは各制御文を表現するものである。文の種類は、Opcode()メソッドでア クセスすることができる。bodyを持つ場合には、getBodyでその文にアクセス することができる。したがって、各Blockをトラバースする場合には、Opcode により、bodyを持つかどうかを調べ、分岐すればよい。また、instanceof演算 子を用いて、サブクラスの種類で分岐してもよい。getBody()で返されるbody 部は、BlockListクラスで表現されており、Blockのdouble linked listである。 トラバースする関数の一部を示す:

void bb_traverse(Block b){
  ...; // bについての何らかの操作
  if(b instanceof IfBlock){
       BlockList then_body = b.getThenBody(); 
       for(bp = body.getHead(); bp != null; bp = bp.getNext())
	   bb.traverse(bp);
       BlockList else_body = b.getElseBody(); 
       for(bp = body.getHead(); bp != null; bp = bp.getNext())
	   bb.traverse(bp);
  } else ...; //その他のブロックに対する処理
}
blockの種類に応じて処理をかえなくてはならないので面倒な処理となる。
そのために、Block構造はBlockIteratorを使って、トラバースすることがで
きる。BasicIteratorは、blockをトラバースするためのイタレータのア
ブストラクトクラスであり、サブクラスとしてbottomupBlockIteratorと
topdownBlockIteratorがある。例えば、上の例では、
BlockIterator i = new topdownBlockIterator(b);
for(i.init(); !i.end(); i.next(){
     b = i.getBlock();
     ...; // なんらかの操作 
}
と記述することができる。 また、BasicBlockをトラバースする場合には、BasicBlockIteratorを用いるこ とによって、Blockの構造を気にせずに各Basicブロックをトラバースすること ができる。

各ブロックのBasic Blockは、getBasicBlock() methodを用いて、アクセスす る。Basic blockは、制御フローを含まない連続して実行される文の列を表現 する。各文の式は、Xobjectと使って表現されている。 Statementクラスは、Basic Block内の式に対する式にアクセスするための イタレータである。 Basic Blockクラスのオブジェクトからアクセスする double linked listになっており、先頭と末尾は、それぞれ getHead(),getTail() methodでアクセスする。

BasicBlock bb;
for(Statement s = bb.getHead(); s != null; s = s.getNext()){
      Xobject x = s.getExpr();
      式xに対する操作 ...
}
最後から、順にアクセスするには、
BasicBlock bb;
for(Statement s = bb.getTail(); s != null; s = s.getPrev()){
      Xobject x = s.getExpr();
      式xに対する操作 ...
}
sの直前に文を挿入するには、insert(Xobject) methodを用いる:
Statement s;
s.insert(x);
また、sの直後に文を挿入するには、add(Xobject) methodを用いる:
Statement s;
s.add(x);
また、文sをBasicBlockから削除する場合には、remove() methodを用いる: これにより、前後のdouble linked listから削除される。 文sが属するBasicBlockは、getParent() methodで得ることができる。

条件式として用いる式は、Basic Blockの条件式(Cond Expr)に設定して おかなくてはならない。この条件式は、BasicBlockクラスのgetExpr() method でアクセスする。

BasicBlockの先頭に文を挿入する場合には、BasicBlockのinsert(Xobject) methodを 用いる。

BasicBlock bb;
bb.insert(x);
BasicBlockの末尾に文を加えるには、BasicBlockのadd(Xobject) methodを 用いる。
BasicBlock bb;
bb.add(x);
だだし、条件式がある場合には、条件式の前に挿入されることになる。

Blockに対する操作は、Statementクラスと同様である。

BlockList b_listに対しては、 Blockを作るためには、各サブクラスのコンストラクタにより直接生成できる が、簡単にBlock構造をつくるために、Bconsクラスのstatic member関数とし て、いろいろなBlockに対するコンストラクタの関数が用意されている。 例えば、条件式cと一つの文xをbodyとして持つwhile文に対するBlockを作 るには、直接コンストラクタを呼ぶ場合は以下のようになる。
BasicBlock cond_bb = new BasicBlock();
cond_bb.setExpr(c);
BasicBlock body_bb = new BasicBlock();
body_bb.add(x);
BlockList body = new BlockList();
body.add(new SimpleBlock(Xcode.LIST,body_bb));
Block b = new CondBlock(Xcode.WHILE_STATEMENT, cond_bb, body);
Bcons.WHILEを用いることによって、
Block b = Bcons.WHILE(c,x);
とすることができる。 一つの式xで表現される文をもつBlockは、以下の文でつくることができる。
Block b = Bcons.Statement(x);

以下の例は、各関数の入口において、メッセージをプリントするプログラムに 変換する例である。

class TransPrintEnterMsg implements XobjectDefVisitor {
   public void doDef(XobjectDef d){
      String fname = d.getName();
      Block fblock = Bcons.buildFunctionBlock(d);
      Ident printfId = 
        d.getFile().declExternIdent("printf",Xtype.Function(BasicType.intType));
      Xobject s = 
         printfId.Call(Xcons.List(Xcons.StringConstant("enter "+fname)));
      fblock.getBody().getHead().getBody().insert(Bcons.Statement(s));
      d.setDef(fblock.toXobject());
   }
   public static void main(String args[]){
       XobjectFile f = new XobjectFile();
       f.Input("foo.x");
       XobjectVisitor op = new TransPrintEnterMsg();
       f.iterateFuncDef(op);
  }
}
ここで、declExternIdentは、ファイルを表現するクラスXobjectFileにおいて、 外部シンボルを定義するmethodである。 printfを外部関数として定義し、これに対しメッセージを作り、呼び出す文を 関数本体の先頭に挿入している。

BlockListは、body部に対するCompound Statmentを表現しており、このブロッ クで宣言されているローカル変数のシンボルテーブルをもっている。 新しいシンボルを宣言する場合には、まず、局所変数に対するIdentオブジェ クトをつくり、それをBlockListのシンボルテーブルにaddIdent methodを使っ て、追加する。例えば、int xをBlockList b_listに宣言するには、以下のよ うにする。

BlockList b_list;
Ident local_id = Ident.Local("x",BasicType.intType);
b_list.addIdent(local_id);
ここで、文 x = x+1 をこのb_listの最後に加えるには、
Xobject s = Xcons.Xset(local_id.Ref(),
                       Xcons.binaryOp(Xcode.PLUS_EXPR,local_id.Ref(),
                                       Xcons.IntConstant(1)));
b_list.add(Bcons.Statement(s));
local_id.Ref()は、local_idへの参照のXobjectを作る。アドレスを参照する には、local_id.getAddr()で作ることができる。

BlockListオブジェクトに定義されているシンボルテーブルを検索するには、 findLocalIdent(String)を使う。 あるStatment sから、変数の宣言されているシンボルテーブルを見つけるには、 Statement sの含まれているBasicBlockから、Blockを参照し、それを含む BlockListを見つけることによって、行う。 例えば、以下のようになる。

Statment s;
BasicBlock bb = s.getParent();
Block b = bb.getParent();
for(b_list = getParentk(); b_list != null; 
             b_list = b_list.getParentList()){
	b_list.findLocalIdent(name);
}
同様の操作は、BlockのfindIdent(String) methodで行うことができる。この
メソッドでは、局所変数で見つからなかった場合には大域変数のシンボルテー
ブルを検索する。