そのようにして定めた中間表現が本コンパイラ・インフラストラクチャの高水準中間表現HIR (High-level Intermediate Representation)である。
HIRでは、処理操作はオペレータとそのオペランドとして表現される。オペランドとしては処理操作の結果も許されるので、 オペレータとそのオペランドを線で結んでゆくと、処理操作は木構造として表現できる。処理対象はスカラー量と配列、構造体、共用体として表現できる。
HIRは次の特徴を持つ。
HIRの論理構造を次の図3-1に示す。
HIR // High-level Intermediate Representation. |- Program // Program definition node. |- SubpDefinition // Subprogram definition node. |- HirSeq // Sequence of definite number of HIR objects. |- HirList // List whose elements are HIR objects. |- Stmt // Statement | |- LabeledStmt // Labeled statement. | |- AssignStmt // Assignment statement. | |- IfStmt // If-statement. | |- JumpStmt // Jump (goto) statement (Jump unconditionally). | |- LoopStmt // Loop statement. | |- ReturnStmt // Return statement. | |- SwitchStmt // Switch (case) statement. | |- BlockStmt // Block representing a sequence of statements. | |- ExpStmt // Expression treated as a statement. | |- InfStmt // An information node (pragma, comment line, etc.) | |- SetDataStmt // Statement to specify initial data. |- LabelDef // Label definition node. |- Exp // Expression |- ConstNode // Constant node |- SymNode // Symbol node | |- VarNode // Variable name node. | | |- ElemNode // struct/union element name node | |- SubpNode // Subprogram name node. | |- LabelNode // Label reference node. | |- TypeNode // Type name node. | |- SubscriptedExp // Subscripted variable. |- PointedExp // Pointed object. |- QualifiedExp // Qualified variable. |- FunctionExp // Function call expression. |- PhiExp // Phi function used in SSA |- ExpListExp // Expression representing a list of expressions. |- NullNode // Null (no-operation) node
プログラムには、変数や副プログラムなどの記号が現れる。これらは文字列のままでは扱いにくいので、記号表に登録し、 HIRでは記号表の記載項として扱う。
記号に関する種々の属性は記号表の記載項に付与する形で記録する。これらの属性には、宣言文やプログラム構造などによって決まるものや、 構文解析、意味解析のときに設定されるものが多い。木構造として表したHIRは処理操作を中心として表現するものであり、 宣言文の抽象構文木は表現しない。記号の情報は記号表に記載する。
int fact( int p)
/* fact0.c: Factorial function */
{
if (p <= 1)
return 1;
else
return p * fact(p - 1);
}
(a) 入力プログラム
(prog 1
<null 0 void>
<nullNode 2>
(subpDef 3 void
<subp 4 <SUBP < int > false false int> fact>
<null 0 void>
(labeldSt 5 void
(list 6
<labelDef 7 _lab1>)
(block 8 void file fact0.c line 2
(if 9 void file fact0.c line 4
(cmpLe 10 bool _XId1
<var 11 int p _XId2>
<const 12 int 1>)
(labeldSt 13 int
(list 14
<labelDef 15 _lab3>)
(return 16 int file fact0.c line 5
<const 17 int 1>))
(labeldSt 18 int
(list 19
<labelDef 20 _lab4>)
(return 21 int file fact0.c line 7
(mult 22 int _XId3
<var 23 int p _XId2>
(call 24 int _XId4
(addr 25 <PTR <SUBP < int > false false int>> _XId5
<subp 26 <SUBP < int > false false int> fact>)
(list 27
(sub 28 int _XId6
<var 29 int p _XId2>
<const 30 int 1>))))))
(labeldSt 31 void
(list 32
<labelDef 33 _lab5>)
<null 0 void>))))))
(b) HIR
COINSの英語のドキュメント How to use COINS Infrastructureの中の「1.3.4. HIR Handling」にHIRの作り方の記述があります。また、 SimpleMain.java は作り方を示すサンプルプログラムです。
スコープAの記号表(SymTable): スコープAで定義される記号(Sym)を納めた表記号の探索、登録の際は、副プログラム内の局所的な変数やレジスタなどを納 めた記号表、大域的な変数や副プログラムなどを納めた記号表など、スコープ の入れ子関係を見ながら、入力言語の文法に従って記号の同定を行なう。
コンパイル単位全体で大域的に定義された記号を納めた記号表 |ーー副プログラムAで局所的に定義された記号を納めた記号表 | |ーー副プログラムAの内側のスコープで定義された記号を納めた記号表 | … |ーー副プログラムBで局所的に定義された記号を納めた記号表 |ーー …入力言語がCの場合、記号表の階層は次のようになる。
symTableConst 定数(ソースプログラムに現れるものとコンパイラの生成するもの) symTableRoot HIRに備え付けの記号(基本型など)とコンパイル単位全体で定義された記号 |- symTable A 副プログラムAで局所的に定義された記号、 | | 副プログラムAでコンパイラが生成する(定数以外の)記号。 | |- symTable A_b1 副プログラムAの中のブロックb1で定義された記号。 | |- symTable A_b1_2 ブロックb1の中のブロックb1_2で定義された記号。 | … |- symTable B 副プログラムBで局所的に定義された記号、 | | 副プログラムBでコンパイラが生成する(定数以外の)記号。 | …記号表の記載項(エントリ)を作るのは、Symインタフェースのvar(), subp(), label()などのファクトリメソッドを呼んで行なう。newで直接にイ ンスタンスを作るのを禁じているわけではないが、newで直接に作るときは、 そのための約束事を十分理解して作らないと、相互関連に不具合が生ずる可能 性が高いので、それは勧められない。
フロー情報の表現では、変数やレジスタなどを記号表の参照子として表すこ ともあるが、データフロー方程式を解くときなどは、それらに一意的な識別番 号を付与して、その番号で示すこともある。その場合、 参照子から識別番号を 求めるメソッドや、識別番号から参照子を求めるメソッドなどが用意されている。
Sym // Symbol class (super class of all symbol classes). | // Symbols in the same scope are contained in the same | // symbol table (instance of SymTable). | |- OperandSym // Symbol that may be an operand | | // of executable expressions. | | | |- Var // Variable that can be assigned a value | | | // and variable with const attribute. | | |- Param // Formal parameter class. | | |- Elem // Class for structure element, | | // union element, etc. | | | |- Const // Constant class | | |- IntConst // integer constant | | |- FloatConst // floating constant | | |- CharConst // character constant | | |- StringConst // string constant | | |- BoolConst // boolean constant | | |- NamedConst // Named constant including | | // enumeration constant (other than bool). | |- Label // Statement label class. | |- Subp // Subprogram class (procedures, functions, etc.) | |- Type // Type information class. | |- BaseType // Base types intrinsic | | // to many programming languages. | |- VectorTyepe // Vector (1 dim. array) type. | |- EnumType // Enumaration type. | |- PtrType // Pointer type. | |- StructType // Structure type. | |- UnionType // Union type. | |- SubpType // Subprogram type. | |- RegionType // Region type to define storage area //##24 | | // shared between subprograms. | |- DefinedType // Types defined in a program (by typedef). | |- ExpId // Expression identifier // (generated to be used in data flow analysis, etc.)
symRoot.symTableRoot symRoot.symTableConstに置かれる。
副プログラム定義のときには、その中で局所的に使われる記号を入れる記号表 をpushSymTable(push操作)によって作り、副プログラム終了時にそれに対 してpopSymTableを起動する(pop操作)。副プログラムの中で新しいスコー プが始まる時(宣言を含むブロックなどの時)には、そのスコープ内での局所 的記号を入れる記号表をpushSymTableで作り、そのスコープから出るときに popSymTableを実行する。
popSymTableは、最も最近pushSymTableしたときからあとに追加された記号を 廃棄するのではなく、以後の追加位置を戻すものであって、作成された記号表 は保存されている。したがって、記号表はツリー構造をとる。
記号表Aのもとでpushにより記号表A1を作るとA1はAの子供となる。A1 のもとでpushにより記号表A11を作り、A11をpopしたあと、pushで記号表 A12を作るとする。同じ記号表のもとでpushで作られた記号表は、兄弟とな る。A12をpopしたあと、A1をpopし、そのあとpushでA2を作ると、A2は A1の兄弟となる。上記の場合、記号表は
A -- A1 -- A11 | |- A12 |- A2という階層構造をなす。1つのコンパイル単位に含まれる各副プログラムに対 して上記のことを繰り返すと、記号表のツリーが構成される。記号表のツリー を1つ1つたどるメソッドとしては、次のものがある。
symRoot.symTableRoot symRoot.symTableCurrent symRoot.symTableCurrentSubpのようにして参照できる。
symRoot.sym.defineVar(変数名, 型) symRoot.sym.defineSubp(副プログラム名, 型) symRoot.sym.defineLabel(ラベル名) ・・・のようにして作る。詳しくはSym0インタフェースやSymインタフェースを参照されたい。 最適化などの際に一時変数や内部ラベルなどを作るには、
symRoot.symTableCurrentSubp.generateVar(型名) symRoot.symTableCurrentSubp.generateLabel()のようにする。詳しくはSymTableインタフェースを参照されたい。
SymIterator lSymIterator = symTableA.getSymIterator();で、順次
lSymIterator.next()と繰り返すことによって参照できる。
lSymIterator.nextVar()とすると、変数(Var, Param, Elem)のみが次々と出てきて、変数以外の記号 はスキップされる。Iteratorを使わない場合は、
Sym lSym1, lSym; lSym1 = symTableA.getFirstSym(); lSym = lSym1.getNextSym();のように、記号表の最初の記号を取り出すgetFirstSym()と、ある記号の次の 記号を取り出すgetNextSym()を使って、次々と参照することができる。
SymNestIterator lSymNestIterator = symTableA.getSymNestIterator();としたあと、
lSymNestIterator.next();のようにすると、指定した記号表(symTableA)とその下位のすべての記号表 を全部走査して、そこに含まれる記号を順次参照することができる。このとき、 next()の代わりに
lSymNestIterator.nextVar();を使うと、symTableAとその下位のすべての記号表に含まれる変数を順次参照 することができる。
HIRは多くの言語に対応可能としているため、かなり豊富な機能を持っている。したがってHIR.javaの中には、 単純なコンパイラでは利用しないような機能も含まれている。機能が豊富なことは初心者が理解するのに時間がかかることを意味する。
そこで、HIRに対する簡易インタフェースHIR0.javaと、Symに対する簡易インタフェースSym0.javaを設定し、 HIR0.javaとSym0.javaを理解すれば簡単なコンパイラは作成できるようにした。 実際に、 PL0コンパイラと Simpleコンパイラ はHIR0.javaとSym0.javaを使って書かれている。
これはCOINSを教育用に利用するような場合に有効である。HIR.javaはHIR0.javaを継承し、 Sym.javaはSym0.javaを継承しているので、この簡易インタフェースでカバーしきれなくなった場合、 importの仕方を変えるだけですべての機能を利用できるようになる。
その概要を説明する文書としては、 HIRとLIRの概要(pdf)、 HIRとLIRの概要(txt)、 HIRの概要(pdf)、 HIRの概要(txt)、 HIRの型と意味(pdf)、 HIRの型と意味(txt)、 記号表の概要(pdf)、 記号表の概要(txt) などを参照されたい。