2003/6/22, 11/28, 2004/5, 6/14, 22, 8/15  高水準中間表現の型と意味                    (##25): 2004/08 改訂                    (##24): 2004/05-6 改訂    (##23): 2003/11, (##22): 2003/7, (##21): 2003/6 改訂    (##14): 2002/6, (##11): 2002/1 改訂    (##10): 2001/12, (##9): 2001/10 改訂  本仕様書では、高水準中間表現HIRの型とその表現内容について述べる。 HIRの仕様は入力言語や対象機種によらないものであるが、具体イメージの説 明等にあたって、特定の言語や特定の機種について言及する方がよいことがあ る。特定言語、特定機種について述べるときは、その部分を[[ ]]で囲んで示 す。特定言語、特定機種に対して何をどのように扱うかの細部については、 SourceLanguageクラス、MachineParamクラスで記述されている部分もある。 1. 入力言語からのHIRへの変換過程とHIR-base  入力プログラムをHIRに変換するとき、途中段階で入力言語固有の表現を含 んだ中間表現を経由することがある。そのような表現を含まないHIRをそれら と区別する必要があるときは、入力言語に依存しないHIRをHIR-baseと呼ぶ。 (##10) [[  CのプログラムをHIRに変換するとき、C特有の幾つかの表現を含んだHIR- Cを経由して、共通表現であるHIR-baseに変換する。    C → HIR-C → HIR-base  HIR-Cは、CからHIRへの変換系の複雑さを分割するためと、ソフトウェア 工学的ツールのように、Cの入力プログラムに近い表現で処理する言語処理系 を作りやすくするために設けた表現である。単にHIRと言う場合はHIR-base のことである。HIR-baseでなくHIR-Cについて述べるときは必ずHIR-Cと言 い、"-C"を省略せずに表す。  HIR-CはHIR-baseを包含する中間表現である。HIR-baseになくてHIR-Cに ある表現は、    短絡条件式(&&, ||)    3項演算子(?:) (##9)    埋め込み代入文(式の因子の形をとる代入文)    コンマ演算子    インクリメント、デクリメント(++, --) (##9)    代入と複合された演算子      (+=, -+, *=, /*, %=, <<=, >>=, &=, ^=, |=) (##9)    ポインタに対する加減演算 である。HIR-Cの型には、HIR-baseに変換されるときに細分化されるものがあ る。たとえばHIR-Cのintは、HIR-baseではint, char, enumに細分化され る。また、HIR-CとHIR-baseの間で、同じ表現形式で意味の異なるものとし てはポインタの減算がある(##9)。それがどちらの意味で使われているかは、 演算ノードにつけたフラグで示される(HIR.FLAG_C_PTR)。 (##11) ]] 2.HIR-baseの型 2.1 型の表記 (1)ノードと型の対応  HIRは木構造で表され、それに対応するテキスト表現を持つ。HIRを表わす 木構造のノードには、子供を持たない葉(leaf)と、子供を持つ節 (nonleaf)がある。各ノードは型を属性(attribute)として持つ。leafノー ドは、変数名や定数、副プログラム名など、入力プログラムに現れる1つの記 号等を表す。記号 s を表すleafノードの型は、その記号 s の型と同じであ る(記号表の仕様概要で述べるように、記号には型が付与されている)。 nonleafノードは、1つかそれ以上の子供と、それらを被演算数(オペラン ド)とする演算子で表される。leafノードにもnonleafノードにも、それが 何であるかを表す演算子がコード化されて付与されている。leafノードはオ ペランドを持たない演算子で表される(##10)。nonleafノードは丸括弧( )で 囲んで表し、leafノードと型構成子で生成された型は角括弧< >で囲んで表す (##21)。  演算子 op を表すnonleafノードの型は、そのオペランドに演算子 op を適 用した結果の式の型を表す。その型は、大部分、オペランドと演算子の組み合 わせによってHIR生成時に決めるが、型変換演算子convの結果の型は、上位 構文から見てそこに何の型が付与されるべきであるかという文脈によって決め、 その型をノードに付記する (##9)。特定の型を必要としないノードの型は voidとする。一般に、nonleafは式や文、副プログラムなどを表し、leafは 単純変数や定数、ラベルなどを表すが、それらをひっくるめて、nonleafも leafも広義の式とみなすことがある。HIRのノードの型は、個々の広義の式に 対して、HIR生成時に決めるものである(##9)。 (2)オブジェクト  値を入れる記憶域の領域(記憶場所)をオブジェクトという(##9)。オブジ ェクトの大きさ(size)はバイト数で表す(##24)。void型でない式は値を持ち、 その値を代入演算子(assign)の第1子によって示されるオブジェクトに代入 すると、その値は次に新たな値が代入されるまで保持され、その記憶場所を参 照する式の値は、そこに最後に代入された値となる。(ただし、後で述べるよ うに、volatile修飾子(qualifier)がついているときはそうとは限らない。) 値の記憶場所としてのオブジェクトを表す式が左辺値(l-value)である。オ ブジェクトの型は、そこに保持される値の型である。一般に、変数は値を代入 できる記憶場所として表現されるが、const修飾子がついていて値を代入でき ない記憶場所も変数で表すことにし、あとで述べる配列や構造体、共用体を格 納する記憶場所も、それぞれ、配列変数、構造体変数、共用体変数というよう に、変数の一種とみなす。そうすると、変数はオブジェクトを表す。ただし、 変数ではない式として表されるオブジェクトもある。配列、構造体、共用体は オブジェクトであり、これらを表す式は左辺値である(##9)。しかし、左辺値 には、const修飾子を持つものなど、代入文の左辺にできないものもある。ス カラ定数は必ずしもオブジェクトではないが、集成体定数はオブジェクトであ る(##21)。  オブジェクトは入力プログラムの宣言に対応して作られるばかりでなく、プ ログラムの実行中に作られることもあり、あるオブジェクトの一部を別のオブ ジェクトとして使うこともある。どの記憶場所をどのオブジェクトとして使う かはHIRでの表現に従って決める。 (3)基本型  HIRに備え付けの基本型(basic type)としては   short int 短い整数   int 整数   long int 長い整数   long long int 長々整数   float 浮動小数点数   double 倍精度浮動小数点数   long double 長い倍精度浮動小数点数   char 文字   bool false, trueを値とする(列挙に似た)型 (##22)   unsigned short int 符号なしの短い整数   unsigned int 符号なしの整数   unsigned long int 符号なしの長い整数   unsigned long long int 符号なしの長々整数   unsigned char 符号なしの文字   offset 構造体要素や配列要素の変位、または               オブジェクトのサイズを表す数(バイト単位)   void 値を持たない型(空集合) がある。short int, int, long int, long long intは符号つき整数型と総称 し、unsigned short int, unsigned int, unsigned long int, unsigned long long intは符号なし整数型と総称し、両者を合わせて整数型と総称する。 float, double, long doubleを浮動小数点型と総称する。charとunsigned charを文字型と総称する。あとで述べるように、charは整数型に変換できる が,その変換は処理系定義(implementation defined)の動作である。これら の基本型の大きさが何バイトで表現されるかは対象機種などによって異なるが、 それをパラメータ化することができる(MachineParamクラス)。(##12)。  注:処理系定義(implementation defined)の動作または値とは、本コンパイ ラインフラストラクチャを利用するコンパイラ開発者がその内容を定めてよい 動作または値であるが、どのように定めたかを文書化しなければならない事項 である。  定数は型と値をもつ。値の内部表現は処理系定義である。値には外部表現で ある表記(表示形式)も対応する。HIRにおける表記は入力言語における表記 と異なることもある。HIRの値の表記は中間表現の表示などに使われる。入力 言語の定数をHIRの表記に変換するのは、SourceLanguageクラスの makeIntConstString, makeFloatConstString, makeStringBodyなどのメソッ ドによって行う。 (##21)  HIRでは、intの定数は数字の列で表記する(##25)。long intの定数は数字 列の後ろにLをつけた形で表記する。long long intの定数は数字列の後ろに LLをつけた形で表記する。unsigned intの定数は数字列の後ろにUをつけた 形で表記し、unsigned long int, unsigned long long intの定数は、それぞ れ、数字列の後ろにUL, ULLをつけた形で表記する(##25)。たとえば1という 定数はどの型であるかにしたがって、1, 1L, 1LL, 1U, 1UL, 1ULLという表記 をもつ。 (##21)  浮動小数点定数の表記は、基本的にC言語と同じであるが、型を区別するた めに、floatであれば末尾にF、doubleであれば末尾にD、long doubleであ れば末尾にLをそれぞれつける。 (##25)  文字列は文字の配列として表す。文字列定数はstringと呼ぶ表記をもつ。 文字列の表記においては、入力言語によって開始終了の印やエスケープ文字が 加わったりするが、それらを除いて表現したい文字そのものの列からなるもの を文字列本体と呼ぶことにすると、HIRでは文字列定数の表記は文字列本体の 前後に引用符(")をつけた形をとる。ただし、印字するときは、文字列本体 には印字できない文字が含まれたりするので、コンパイラ記述言語である Javaの表記に合わせてエスケープ文字を補って印字する(##25)。空文字列は ""で表す。入力言語における表現形式とHIRにおける表記の間の相互変換を行 うメソッドは、SourceLanguageクラスに用意する(##22)。 (##14)  charの値にはint型の文字コード(character code)が対応する。 unsigned charの値にはunsigned int型の文字コード(character code)が 対応する。boolの値はtrueとfalseである。boolの値には順序数(order number)と呼ぶ整数が対応し、trueの順序数は1、falseの順序数は0である。 offsetは構造体要素や配列要素の変位、あるいはオブジェクトの大きさを表 し、int または long int, long long intの内部表現値が(後述の元の型と して)対応するが、それがint , long int, long long intのいずれであるか は対象機種によって異なる(##10)。  ラベルは値を持つオブジェクトではないが、それを表す記号の型はvoidで あるとする。  多くの言語には、上記の基本型のいくつかに対応する型が備えられている。 それぞれの言語のどの型がHIRのどの型に対応するかは、その入力言語に対す るコンパイラの言語解析部によって決められる。 (##11) [[  Cのint, short int, long int は、それぞれ、HIRのint, short int, long int に変換し、Cのsigned int, signed short int, signed long int は、それぞれ、HIRのint, short int, long int に変換する。Cのcharを HIRのcharに変換するかunsigned charに変換するかは処理系定義である (SourceLanguageクラス参照)。 (##12)  HIR-baseのchar, bool, offsetは、あとで述べるように、HIR-Cでは整数 型として扱うことができる。  Cでは文字列の末尾に終わりの印エ0をつけるが、これは文字列本体には含め ない。(##14) ]] (4)型構成子と元の型  基本型やその他の型から新しい型を構成するための型構成子として、   pointer ポインタ(オブジェクトを指し示す型)   vector 一次元配列   struct 構造体   union 共用体   enum 列挙(enumeration)   subprogram 副プログラム   typedef 型名定義 がある。  列挙を構成する要素としての列挙子(enumerator)には、順序数(order number)と呼ぶ整数型の値が対応する。boolは、順序数を持つなど、列挙と 共通する点が多いが、列挙とは別の型である(##22)。ポインタには、値として 整数型の記憶番地(address)が対応する(##25)。  型には型修飾子 const, volatile をつけることができる(##12)。型 t に型 修飾子をつけた型 t1 は t とは異なる型とするが、演算に関しては t と t1 は同じ扱いとし、明示的型変換を必要としない。constをつけた型の変数に対 しては代入操作(assign演算子による操作)を行うことはできない。 volatileを付けた型の変数に対しては、コード最適化で代入や参照を省略す ること、ならびにそれらの順序を変更することはできない。 (##10)  型構成子(type constructor)は、新たな型を導入するとともに、導入された 型(introduced type)に対する型名定義を行うのに使う(##22)。型構成子によ って新たな型 t を導入したとき、tとは何かを定義するのに使う型を「元の 型(origin-type)」という。元の型については、型構成子ごとに説明する。導 入された型に対する演算は元の型にさかのぼって行うが、導入された型の演算 では元の型の演算に制約が加わることがあり、それについては個別に説明する。 基本型は、特定の指定がなければ元の型を持たないが、入力言語によっては元 の型を与えることができる。offsetの元の型は、対象機種のパラメータによ って(long, long longなどと)選択することができる(MachineParamクラス 参照)。(##11) [[ 入力言語がCのときは、charやboolの元の型をintと指定する。 (SourceLanguageクラス参照) ]]  相異なる型 t1 と t2 の間では、元の型が同一であっても、それに適用する 演算子がオペランドとして同じ型のものを要求するならば、型変換の規則に合 わせて一方を他方の型に変換する(##10)。ただし、あとで述べるように、いく つかの例外がある。元の型が同じ場合、値の表現範囲(値域)の違いなどはあ るが、t1とt2に共通の表現範囲にある値に対しては、型の変換は見方を変え るだけであって、値の変更は伴わない。typedefによる型名定義で異なる型名 が導入されたときなど、元の型が同じで実質的値変換を必要としない場合はよ く生ずる(##22)。 (##9)  導入された型の値域が元の型の値域と同じ場合、ならびに型修飾をつけた型 の場合、導入された型は元の型の演算を継承する。その場合、HIRの表現とし ては、導入された型を元の型にさかのぼった形で表現してもよいし、導入され た型として表現してもよい。この選択はimplementation definedである。導 入された型の値域が元の型の値域と異なる場合、導入された型と元の型の間で、 同じ演算子でも異なる意味を持つことがある。 (##11)  元の型の連鎖を次々とさかのぼってゆくと、基本型等に行き着いて、それ以 上さかのぼれなくなる。それを究極の元の型(final origin type)という。  (##24) (5)型の表記  HIRをテキスト表現するとき、型もテキストで表す(表記する)が、そのと き、基本型を1つの短い識別子で表せるようにするため、その表記(表示形 式)を定めておく。   表記     基本型       説明 int int (integer) short short int (short integer) long long int (long integer) long_long long long int u_int unsigned int u_short unsigned short u_long unsigned long int u_long_long unsigned long long int char char (character) u_char unsigned char float float (floating point number) double double (double floating point number) long_double long double bool bool (boolean type, similar to enum) void void (has no value) offset offset (structure element offset, vector element offset, object size, pointer difference) (##25)  型構成子に対しても、短く表すための表記を定めておく。 ENUM enum PTR pointer VECT vector STRUCT struct UNION union SUBP subprogram TYPEDEF typedef 複合された構文で型を表記するときは、型を括弧で囲んで混乱を防ぐ。  整数型と浮動小数点型、文字型、ポインタ型、列挙型、bool型、offset型 はスカラ型と総称し、配列(vector)と構造体(struct)、共用体(union)は 集成体(aggregate)と総称する。型の区分を以下にまとめて示す。(##10)  究極の元の型が同じになる型は同じ区分の型である(##24)。  基本型    整数型     符号つき整数       int, short int, long int, long long int     符号なし整数       unsigned int, unsigned short,       unsigned long int, unsigned long long int    文字型      char, unsigned char    浮動小数点型      float, double, long double    bool    void    offset  型構成子    ENUM, PTR, VECT, STRUCT, UNION, SUBP, TYPEDEF  スカラ型    int, short int, long int, long long int,    unsigned int, unsigned short,    unsigned long int, unsigned long long int    char, unsigned char,    float, double, long double,    bool,    offset,    ENUM, PTR  集成体    VECT, STRUCT, UNION (6)型の導入  導入された型の区分は、元の型と同じである。すなわち、元の型が整数型で あれば導入された型も整数型であり、元の型がスカラ型なら導入された型もス カラ型である。2つの導入された型が同じか否かは、言語によって構造同値、 名前同値などの違いがあるので、SourceLanguageクラスのメソッドで判定す るのがよいが、あとで述べるいくつかの場合については、同値関係が定められ ている。(##11)  記憶場所としてのオブジェクトは、変数として表現されることが多いが、ポ インタ式としても表現される。型 T のオブジェクトを指すポインタの型は と表す。PTR T を型とするポインタの指すオブジェクトの型は T である(##9)。 を型とするポインタは、任意の型のポインタとの間で相互に変換できる。 (6-1) 配列  配列は、同じ型の要素の順序づけられた列である(##24)。配列の大きさは、 要素の大きさに要素数を乗じた数で表される(##24)。型 T の要素 n 個からな り、添字値の下限がlbである配列の型は (##14) と表す。 を型とする配列の要素の型は T である(##9)。多次元 配列の型は、 > のように、配列の配列の型として表す。要素数と下限は、整数型定数ばかりで なく、整数型の式として表されることもある(##24)。コンパイル時に要素数や 添字下限値の指定されていない配列の型は不完全型(incomplete type)であ って , のように、要素数や添字下限値の部分をnullとして表記する(##10)。不完全 型の配列型は、その要素型と同じ型を要素型とする配列の型との間で、演算に 際して型変換を必要としない。(##11) [[ 1つの配列の要素は、通常、連続する記憶場所に置かれるが、HIRの仕様とし てはそれを強制しない。したがって、添字の昇順に配置されることも強制しな い。配列要素の配置仕方とアクセスの実現方法は処理系定義である(##24)。 ]] (6-2) TYPEDEF  TYPEDEFは、Tで型を表し、 (typedef T name) という表現(宣言)により、ある型Tにnameを名前としてつけることを表す もので、この表現(宣言)により という名前の型、あるいは入力言語ごとに定める表記の型を導入し、演算など の実行に際してはその型の扱いはTと同じとするということを表現する。ここ でTは導入された型の元の型であるという。(元の型の値域と 異なる値域を持つ導入された型の定義しかたはあとで定める(今はまだ定めな い))。nameは通常は識別子であるが、入力言語によってはそうでない表現も 例外的に認める。 (##11)  導入された型を持つ式の演算は、元の型の規則に従って行う。元の型がまた 導入された型であれば、その元の型をたどって行って、最後に行き着いた(元 の型が与えられなくなるところまで行き着いた)元の型の規則に従って行う。 ただし、導入された型と元の型の間で値域に違いがある場合は、演算内容が一 部異なることがある。(##11) [[  Cの場合、   typedef int cm;   typedef cm Length;   Length leng1; とすると、という型が導入され、leng1の型 はとなり、の元の型はの元の型はintとなる。そして、leng1に対する演算はintとし て行われる。すなわち、導入型名の一形式としてという形を 許す。(あとで述べるように、, , という型も導入型の名前として許す。) (##11)  Pascalの宣言 type price = integer; は、HIRでは (typedef int price) という宣言に対応し、これによりpriceという型が導入されるとし、その元の 型はint であるとする。 (##11) ]] (注1:  TYPEDEFを型構成子としないで、HIRを生成する時に導入された型を元の型 で置き換えてしまうという方式も実現方式として許す。現在のCparserはその ようにしているので、導入した型を示す方式だとCparserの改訂が必要である が、元の型に置き換えてしまう方式だとCparserの改訂は不要である。ただし、 その方式では、導入された型名が異なると異なる型として扱う言語の場合は適 用できず、また、デバッガなどで入力プログラムにおける表記を要求する場合 にもうまくない。Cの場合は型名で区別しないので、デバッガの問題を別にす れば、CからHIRを生成するときに導入された型を元の型で置き換えてしまう ことにしてもよい。) (##11) (6-3) 列挙  列挙は > と表記され、列挙子指定は識別子としての列挙子(enumerator)とその順序数の リスト   <列挙子 順序数> で表記される。順序数(ordinal number)は整数定数である。列挙子に対応する 順序数は、列挙子の並び順に0, 1, 2, 3, ... と指定されることが多いが、 必ずしも連続した数である必要はなくて、 > のような指定も許される(##10)。列挙子の型はenumであり、その元の型は整 数型で、値は対応する順序数である。(列挙子の元の型がどの整数型であるか はMachineParamクラスで指定する。(##11)) [[  Cで列挙にその名前を表す識別子tがタグとしてついていれば、と いう型が導入されたとして、その列挙型を で表す。識別子がついて いなければHIRに変換する時にタグ名t1を生成し、という型が導入 されたとしてで表すように変換する。すなわち、導入型名の一形式 としてという形を許す。の元の型は列挙宣言そのもの からタグ名を除いたものとして表す。たとえば、Cにおける enum signal {RED, YELLOW, GREEN} s1; で宣言されるs1の型はと表し、の元の型は >> であるとする。すなわち、これは変数s1を宣言すると同時に (typedef >> ) で、 >という2つの型 を宣言しているものと見なす。また、Cにおいて enum {OFF, ON} flag; で宣言されるflagの型は、コンパイラで生成されたタグ名をt1として とあらわし、その元の型は >> であるとする。 ]] (##11) (6-4) 構造体と共用体  構造体は、それを構成する要素の順序づけられた列である。構造体の大きさ の計算しかたは処理系定義である。共用体は、それを構成する要素の記憶場所 が重なり合った集成体であり、その大きさは処理系定義であるが、最も大きい 構成要素の大きさを下回ることはない。構造体の要素と共用体の要素に対する アクセスの実現方法は処理系定義である。 (##24)  構造体と共用体は、それぞれ、STRUCTまたはUNIONの指定に続いて、その 要素指定を本項における型表記にしたがって書き並べた形をとり ... > または ... > の形で表記する。これは、それぞれ、Type_1, Type_2, ..., Type_nを型とす る要素Name_1, Name_2, ..., Name_nで構成される構造体または共用体の型の 表記である。  共用体 ... > は、その各要素 Name_1, Name_2, ..., Name_n の記憶場所の先頭位置が同じ であることを意味する。 (##23)  構造体と共用体に対して、まず(typedef null 導入型名)で導入型名を不完 全型として定義し、あとで(typedef 構造体 導入型名)または(typedef 共用体 導入型名)でそれを完全な型として定義することができる。不完全型はポイン ト先の型などとして指定することができる。 (##10) [[  Cのstruct宣言で struct point { int x; int y; } s1; のようにタグ(上記の例ではpoint)がついていれば、これは構造体s1を宣 言すると同時に、 という導入型名を導入し、それを使って 構造体 s1の型を宣言したものとみなす。タグ名がついていなければHIRへの 変換時にコンパイラでタグ名を生成して同様に表す。共用体の場合も同様に という型名を導入して宣言したものとみなす。すなわち、導 入型名の一形式としてという形を許す。し たがって、Cの struct point { int x; int y; } s1; union value { int iValue; float fValue; } u1; struct { char* name; int price; } b1; では、b1の宣言で生成されたタグ名をt2とすると、 s1 の型:   (##9) u1 の型:   (##9) b1 の型:     の元の型: >    の元の型: > の元の型:  name> > となる。  例えばCにおける typedef struct tnode *TreePtr; typedef struct tnode { char *name; TreePtr left; TreePtr right; } TreeNode; TreeNode root; では、rootとleftの型は rootの型: leftの型: であり、 の元の型は の元の型は > である。は、は じめは不完全型として定義され、あとで完全型として再定義される。 typedef int Int; typedef Int *IntP; IntP p; に対しては > という型が導入され、pの型はの元の型は >>、の元の型はintとなる。 ]] (##11) (6-5) 副プログラム  副プログラムの型は、第2項で引数の型のリスト、第3項で可変個数引数の 有無を表すbool定数、第4項で返却値の型を表し、 bool 返却値の型>  (##9) と表記する。引数のない副プログラムでは第2項は空のリスト < > である。 可変個数引数の有無を表すbool型の第3項は、falseなら無し、trueなら有 りを意味する。返却値を持たない副プログラムでは第4項はvoidとする。 [[ たとえばCの int (*comp)(void *, void *); に対して、compの型は > false int> > > である。Cの場合、副プログラムを表す式が&とsizeofのオペランド以外のと ころで使われると、それは副プログラムへのポインタと解釈されるので、comp を呼び出す式 comp(i, j), (*comp)(i, j) の副プログラム指定部分の型は、いずれも > false int> > > である。少し複雑な例として int *fpi(); /* Function returning pointer to int. */ int *(*pfpi)(); /* Pointer to function returning pointer to int. */ int *(*fpfpi())() /* Function returning pointer to function */ { /* returning pointer to int. */ return pfpi; } ... pfpi = fpi; pi1 = pfpi(); i5 = (*fpfi())(); という文の列に対して、それぞれの副プログラムないし副プログラム式の型は int *fpi(); false > int *(*pfpi)(); false >> int *(*fpfpi())() false false >>> { return pfpi; false >> } ... pfpi = fpi; false >> pi1 = pfpi(); false >> i5 = (*fpfi())(); false false >>>> となる。 (##11) ]] (6-6) 型修飾  型 t に型修飾子をつけた型は       のように、t に型修飾子を追記した形をとり、その元の型は t とする。演算 は元の型として行う。同じ型のオペランドを要求する式の中で、2つのオペラ ンドの型の間で型修飾子以外の部分は同じである場合は、型変換操作を挿入す る必要はない。 (##11) 2.2 演算式の型  演算式の型は、オペランドの型と演算子によって決めるものが多いが、conv のようにHIR-baseに変換するときに入力言語の仕様に従って決めるものもあ る。各々の演算子はそれに合った型のオペランドを要求するので、それに合う ように、入力言語の解析部では、必要に応じてオペランドを明示的に変換しな ければならない。演算式の型について以下で式ごとに個別に説明するが、型変 換の節も参照されたい。  HIR-baseの文法は複数言語、複数機種に共通であるが、その具体的表現 (インスタンス)には言語や機種に依存する値を持つ項目がある。すなわち、 HIRのインタフェースは複数言語、複数機種に共通であるが、そのインスタン スには言語や機種に依存する値を持つフィールドがある。具体的コンパイラは 特定の入力言語、特定の対象機種に対するものなので、このような依存性があ ってもさしたる支障はなく、HIRの処理プログラムを複数言語、複数機種に共 通の形で作ることができる。 (##9) (1)定数  HIRでは、整数型としてはint, long int, long long int, unsigned int, unsigned long int, unsigned long long intの定数があり、浮動小数点とし てはfloat, double, long doubleの定数がある(##25)。入力言語のどの定数 がHIRのどの定数に対応するかは、その入力言語の言語解析部で決められる。 (##21) [[  C言語の符号付き整数型、符号なし整数型、浮動小数点型の定数は、HIR- baseでもそれに対応したHIRの型の定数である(##22)。sizeof演算子を作用 させた結果はoffset型の定数である。(ただし、その値は、同じ入力プログラ ムに対しても、対象機種が異なると異なる場合がある(##10)。)  HIR-baseでは、Cと違って、文字定数はint型ではなくchar型であり、 enumで定義した名前付き定数としての列挙子はint型ではなくenum型である。 文字列は、定数としての表記はあるが型としては文字の配列とするので、文字 列定数をその構成要素である文字に分解するときは、charの配列として扱う。  (##14) ]]  整数型とoffset型、 enum型の定数は、一般に、(2進数で表される)内部 表現値(internal value)を持つが、値がJavaの整数として表現できる範囲を 越えている場合は、isOutOfValueRange()メソッドでtrueが返却され、正しい 内部表現値を持たない。定数畳み込みなどの中間表現変換操作は、内部表現値 がJavaの整数として表現できる範囲でのみ行う。浮動小数点型の定数も一般 に内部表現値を持つが、それがJavaの浮動小数点数として表現できる範囲を 越えている場合には、isOutOfValueRange()でtrueが返却され、正しい内部表 現値を持たない。文字定数の内部表現値は、それに対応するintの内部表現値 またはunsigned charをintに変換した内部表現値をとる。 (##9)  文字列定数は、それに対応する文字列本体のはじめと終わりに"を付けた形 で内部表現する。 (##14) [[ 文字列本体は、入力言語がCの場合、両端の引用符 " を削除し、中間の escape character エを解釈し、終端の0x00 を削除した文字列である。  (##14) ]] 型がTで値がvの定数を表すHIRのノードは と表す。たとえば整定数123を表すHIRノードは と表す。(##21)  bool型定数には , がある(##24)。 [[ C言語の文字列定数を表すノードは、末尾につく0x00も文字配列の要素と数 えて "abc"> のように表す。 ]] (##21)  空記号nullは式の欠如を表す。(木構造におけるNullNodeは何の作用もし ない葉を表すものであり、それはnullではない。NullNodeの型はvoidであ る。) (##22) (2)変数  入力言語の型TがHIRの型tに対応するものであれば、入力言語のT型の変 数はHIRではt型の変数となる。tがスカラ型であればそれはスカラ変数であ り、集成体型であれば集成体変数である。 [[  C言語のshort int, int, long int, char, float, double, long double, unsigned short int, unsigned int, unsigned long int, unsigned char の 変数は、それぞれ、HIR-baseでもそれに対応したHIRの型の変数である。Cの 宣言において、Tは型指定とし、Dは宣言子(declarator)とするとき、 T D[n] で宣言されるDの型は、型Tのn個の要素からなる配列 である。ここにnは符号なし整数定数とする。また T D[ ] で宣言されるDの型は、型Tの個数不定の要素からなる配列 である。 T *D で宣言されるDの型は、型Tのオブジェクトを指すポインタ である。enum, struct, union, 関数の宣言については、先に述べた形でその 型が決められる。 char *s; では、sの型は文字を指すポインタ とする。 ]]  変数vを表すleafノードは、vの型をTとすると と表す。  要素の型をTとする配列 a の第 i 要素は、aとiに対するHIRの表現をそ れぞれa', i'とすると、 (subs T a' i') と表される。a'の型は、配列の要素数をn、添字下限値をlbとすると、である。aとiが変数を表すleafノードであれば、上記は (subs T a> ) と表される。この表現から配列 a の第 i 要素の位置を算出するには、添字値 が1つ進むときの記憶番地の変位の差分等が必要になるが、その計算に使う要 素のサイズや語境界は対象機種に依存する。a'は、あとで述べるように、配列 aを指すポインタ式、または配列aの先頭を指すポインタ式であってもよい (##14)。多次元配列は配列を要素とする配列として表現する。 (##21)  構造体sの要素xは、xの型をTとし、s のHIR表現をs'とすると、 (qual T s' ) で表す。たとえば以前にあげた例では (qual int > s> ) のように表す(##21)。共用体の場合も、STRUCTがUNIONに変わるだけで、あ とは同様である。要素xのoffsetを求めるには、Elemインタフェースのメソ ッドevaluateDispを使って x.evaluateDisp() のようにする。通常、その値は定数ではあるが、定数式として表されている場 合もあり、定数値として確定するか否かはisDispEvaluableで確認できる (##9)。  ポインタpが指す構造体や共用体のT型の要素xは、p, xのHIR表現をそ れぞれp', x' とすると、 (arrow T p' x') と表す(##21)。p, xがleafノードであれば (arrow T p> ) となる。ポインタpの指すオブジェクト自体(スカラ型)の値は、 (contents T (p' ) で表される。  ポインタpが配列aの先頭を表していて、その値が変更されることがなく、 アドレスも取られていなくて、 (contents int (add p> (mult offset (conv offset )))) のように、第i番目の配列要素を指し示すという形でのみpを使っており、配 列の要素数と添字下限値がわかっているならば、aの要素の型をTとすると、 pを含む式の指す第i番目の配列要素は、ポインタを配列にundecayしてsubs による配列要素と同じ表現にしてもよい(##24)。配列要素の表現にする方が、 ポインタ表現より、最適化や並列化が行いやすい場合が多い(##24)。上記の場 合、pが配列の先頭要素へのポインタという形をとっているならば、 (subs T (undecay p> (##22) ) (##22) (i' int) ) と表してもよく、pが配列自体へのポインタという形をとっているならば、 (subs T (##22) (contents (##22) > p> ) (##22) (i' int) ) (##22) と表してもよい(##25)。ここで、(i' int)は、iに対するHIRの表現であり、 n, lbはpの指す配列の要素数と添字下限値である。 (##21) [[ Cの関数で配列として宣言されている仮引数が上記の条件を満たしているなら ば(##24)、その配列要素はsubsで表現してもよい。たとえば、 void f(int a[10], int b[3][5]) { ... } では、a[i]、b[j][k]を、それぞれ、 (subs int (undecay a> ) ) (subs int (subs (undecay ) > b> ) (##22) ) ) と表してもよい。Cの言語仕様では配列形式で表記した仮引数はその先頭要素 を指すポインタを意味するので、この例ではint a[10]の10と int b[3][5] の3は、言語仕様上は要素数が10や3であることを保証するものではないが、 仮引数として渡された配列が素直な配列としての使い方しかされていず、上記 の条件を満たしていると判断される場合は、配列要素は最適化や並列化の重要 な対象なので、ポインタとしての表現でなく配列要素としての表現にしてもよ い(##25)。ただし、配列として宣言された仮引数に対応する実引数が宣言通り の要素数を持っているか否かは一般には検証できないので、最適化や並列化の 変換にあたって、対応関係を満たしているものとして変換するのは、コンパイ ル時のオプションでその旨指定されている場合に限るべきである。なお、 int f(int (*p)[10], (*q)[4][5]) { int i, j, k, x; .... x = (*p)[i] + (*q)[j][k]; .... } のような書き方をする場合、(*p)は要素数10の配列、(*q)は要素数[4][5]で 示される2次元配列と確定しているので、無条件に配列として扱うことができ る。 (##24) ]] (##21) (3)算術式  加減乗除、剰余を表す算術演算子add, sub, mult, div, modは、整数型ま たは浮動小数点型のオペランドから、そのオペランドと同じ型の結果を生成す る。符号つき、符号なし、short, long, double等の区別は、オペランドの型 によって定まる(##9)。これらの算術演算子において、2つのオペランドは、 特別に説明する場合を除き、同じ型でなければならない。型の異なる2つのオ ペランドに対して算術演算子を作用させようとする場合は、表現能力の高い方 に合わせて格上げするよう、一方のオペランドを明示的に変換してから演算す る形に構成する。ただし、型修飾子つきの型とその元の型(型修飾なしの型) との間では変換せずに演算できる(##10)。その変換は、HIR-baseの表現を生 成する時に入力言語の仕様に合わせて行う。  符号反転させる単項マイナス演算子negは、符号つき整数、offset、または 浮動小数点数のオペランドに対しては、同じ型で符号を反転した結果を生成す る。符号なし整数や文字など、上記以外の型のオペランドに対してnegを作用 させようとする場合は、HIR-baseに変換するとき、符号つき整数への明示的 変換を行ったものをオペランドとしなければならない。 (4)ポインタ式 (##9)  HIR-baseでは、ポインタに対して、第1オペランドをポインタとし第2オ ペランドをoffsetとする加減算と、第1オペランドをoffsetとし第2オペラ ンドをoffsetまたは整数型とする加減算、ならびに第1オペランドと第2オ ペランドを同じ型のポインタとする減算のみが算術演算として許される。第1 の場合、ポインタとoffsetの演算結果の型は第1オペランドのポインタの型 と同じである。add演算子では第1オペランドのポインタの表す記憶番地に第 2オペランドのoffsetの値を加えた記憶番地を結果のポインタのポイント先 記憶番地とする。sub演算子では、第1オペランドの表す記憶番地から第2オ ペランドの値を減じた記憶番地を結果のポイント先記憶番地とする。第2の場 合の結果はoffset型である。第3のポインタ間の減算では、第1オペランド のポイント先記憶番地から第2オペランドのポイント先記憶番地を減じた値を 持つoffset型の結果を生成する。  HIR-baseでは、offsetとoffsetの加減算はoffset型の結果を生成し、 offsetに整数を加減算、または乗算するとoffset型の結果を生成し、offset をoffsetで除算すると整数型の結果を生成する。offsetの除算は、sizeofで 生成されるoffset値などを利用して、配列の要素数を求める場合などに用い る。 [[  HIR-Cにおけるポインタの加減算では、add, sub は、それぞれ、型Tのオ ブジェクトを指すポインタpと整数iをオペランドとして、pにsizeof(T)*i を加える、または減じるが、HIR-baseでは、add、subのオペランドはポイン タ型とoffset型とし、すでにこのようなsizeof(T)を乗じる演算がなされた 結果がoffsetオペランドとして与えられているものとする。すなわち、HIR に変換するとき、そのような乗算を明示的に挿入する。同様に、Cではポイン タの差分をポイント先オブジェクトの個数としての差分を求める形をとるが、 HIR-baseでは、sub演算子により記憶番地の差分をとったとき、オブジェクト の個数としての差分を求めるには、sizeof(T)で割る演算を明示的に表現しな ければならない。HIR-Cでのみ使えるoffset演算子では、ポインタ間の差分 を要素数で表す。HIR-CはHIR-baseを拡張した表現なので、ポインタの差分 をoffset演算子でなくsub演算子で表すときは、HIR-baseと同じく、 sizeof(T)で割る演算を明示的に表現する。HIR-Cでも、ポインタと整数型で なく、ポインタとoffset型の表現をとる場合はHIR-baseと同じ意味になる。 ]]  HIR-baseにおけるポインタ関連の演算をまとめると、次のようになる。  演算子 第1     第2    結果      オペランド  オペランド   add ポインタ  offset   ポインタ   sub ポインタ  offset   ポインタ   sub ポインタ  ポインタ  offset  (要素数ではない)   add offset   offset   offset   add offset   整数型   offset  (##10)   sub offset   offset   offset   sub offset   整数型   offset  (##10)   mult offset   整数型   offset   div offset   offset   long   (##22)   neg offset     offset  (##10)   offset ポインタ  ポインタ  long  (要素数)HIR-Cでのみ 使用 (##24) [[  HIR-Cにおけるポインタ関連の演算では、上記に加えて、ポインタと整数、 整数とポインタなど、C言語で許される表現が許される。(##24)。 ]]  アドレス演算子addrは左辺値または副プログラム型をオペランドとし、オ ペランドの型をTとすると、Tへのポインタ を結果の型とする。  間接演算子contentsは、ポインタ型のオペランドpを引数とし、pのポイ ント先のオブジェクトの値を結果とする。pが型Tへのポインタであるとする とcontentsの型はTとなる。  decayは配列をオペランドとする演算子であり、配列の先頭要素へのポイン タを結果とし、結果の型は、配列要素の型をTとすると、Tへのポインタ である。decayを文字列定数に対して作用させると、文字列の先頭文 字を指すポインタ(型は)が得られる(##21)。  undecay演算子は、第1オペランドとしてポインタをとり、第2オペランド として要素数を表す整数式、第3オペランドとして添字下限値を表す整数式を とる。undecayの第1オペランドの型を 、第2オペランドを整数 n 、 第3オペランドをlbとすると、undecayの結果は、型T の要素をn個もつ配 列であり、その型は    である。undecayの第2オペランドや第3オペランドがnullであると、要素 数等の該当部分が不定の配列を表し、その型は    のように、該当部分がnullの配列となる。  addr, contents, decay, undecayの違いを型に注目して見ると次の通りで ある。   addr: T →   decay:       string → または       すなわち、配列と文字列定数(string)に対して適用する。       関数にはdecayを適用しない。   contents: → T   undecay:       すなわち、配列要素へのポインタの型から配列型に変換する。 (5)比較式  大小比較演算子cmpGt, cmpGe, cmpLt, cmpLeは、整数型または浮動小数点 型、列挙型、bool型、offset型、ならびにポインタ型の同じ型の2つのオペ ランドの値に対して、それぞれ、第1オペランドが第2オペランドより大きい、 大きいか等しい、小さい、小さいか等しいならばtrue、そうでなければfalse のbool型の結果を生成する。列挙型は順序数の大小によって比較する。bool 型では、falseは0、trueは1の順序数を持ち、その順序数によって大小比較 を行う(##22)。offset型は元の型によって大小比較を行う。 (##10)  等不等比較演算子cmpEq, cmpNeは、スカラ型をした任意の同じ型の2つの オペランドに対して、それぞれ、両者の値が等しい、等しくないによって true、falseのbool型の結果を値とする。  算術式の場合と同様に、型の異なる2つのオペランドに対して比較演算子を 作用させようとする場合は、表現能力の高い方に合わせて格上げするよう、入 力言語の仕様に従って、一方のオペランドを明示的に変換してから演算する形 に構成する。ただし、ポインタの等不等比較では、voidへのポインタとnull は任意の型へのポインタと比較できる。また、型修飾子つきの型とその元の型 (型修飾なしの型)との間では変換せずに比較できる(##10)。 (6)論理式  論理演算子and, or, xorは、bool型の2つのオペランドに対して、それぞ れ、論理和、論理積、排他的論理和の演算を行ない、bool型の結果を生成す る。整数型、offset型に対しては、その内部表現に対する論理和、論理積、 排他的論理和の演算を行ない、オペランドと同じ型の結果を生成する。補数演 算子notをbool型のオペランドに作用させると、trueをfalseに、falseを trueに反転させる。 (##10) (7)ビット演算式  補数演算子notは、浮動小数点型以外のスカラ型(整数型、文字型、列挙型、 bool型、offset型、ポインタ型)のオペランドに対して、その内部表現に対 する1の補数(反転させたビット列)を値とし、オペランドと同じ型の結果を 生成する。and, or, xor をbool以外のスカラ型のオペランドに作用させると、 その内部表現の各々のビットに対する論理積、論理和、排他的論理和を値とす る内部表現を生成し、結果の型はオペランドの型と同じものとする。and, or, xor の2つのオペランドの型は同じでなければならない。(##10) (8)シフト式  シフト演算子 shiftR, shftLL, shiftRLでは、第1オペランドはbool型と 浮動小数点型、ポインタ型以外のスカラ型であり、内部表現に対して作用する (##10)。その第2オペランドは整数型であり、結果は第1オペランドと同じ型 である。算術右シフトshiftRでは、符号ビットを拡張しながら右シフトする。 論理左シフトshiftLLでは、符号ビットも含めて左シフトし、右端の空きビッ トには0を埋める。論理右シフトshiftRLでは、符号ビットも含めて右シフト し、左端の空きビットには0を埋める。 [[ Cから変換するとき、オペランドが符号つき整数か符号つき文字であれば算術 シフトを使い、それ以外なら論理シフトを使う。 ]] (9)その他の演算子  型変換演算子convは、そのオペランドをconvの結果として指定された型 (convノードの型)に変換する。convの結果の型は、convの結果をオペラン ドとして使う演算子がその文脈において要求する型に合わせて、HIR生成時に 決める。voidへのポインタに対しては、任意の型へのポインタとの間で相互 変換ができる。  encloseは、入力プログラムにおいて括弧で囲まれていた式に対応する部分 木を括る働きをするもので、その型はオペランドの型と同じである。  配列と添字から配列要素を作るsubsは、第1オペランドとして配列を表す 式、第2オペランドとして添字式をとる(##14)。その結果の型は、配列要素の 型である。すなわち、第1オペランドの型が であれば結果の 型はTである。添字式の型は整数型とする。各要素が配列の先頭から見て何要 素目にあるかを配列の大きさと添字から算出できる長方形型配列に対しては、 subsによって添字の値から配列要素の位置を容易に求めることができる。部 分配列の大きさが不揃いな配列は、ポインタやポインタを要素とする配列とし て表現する。  SSAのφ関数phiでは、第1オペランドとして結果を表す変数やレジスタ (v) を指定し、第2オペランドとして、変数とラベルまたはレジスタとラベル の対 (v_i L_i) を要素とするリストを指定する。このラベルは、先行する基 本ブロックに付与されたラベルであり、制御の流れがどの基本ブロックを経由 しているかを表現するものである(##10)。 (phi T (v T) (list (v_1 L_1 T) (v_2 L_2 T) ... ) ) これは、L_iをラベルとして持つ基本ブロックから制御がわたって来た場合は それに対応するv_iを結果を表すvとして選択することを表す(i = 1, 2, ...)。v_1, v_2, ... とvの型は同じであり、その型Tがphiの結果の型 である。 演算子とオペランド、ならびに結果の型の関係を表にまとめて示す。○は可 能、×は未定義の意味である(##25)。   型      大小 等不等 符号 整数型への          比較  比較 反転  変換  符号つき整数  ○   ○  ○  自分自身  符号なし整数  ○   ○  ×  自分自身  浮動小数点型  ○   ○  ○   変換  char   ○   ○  ×  文字コードまたは元の型  u_char   ○   ○  ×  文字コードまたは元の型  bool   ○   ○  ×  false 0, true 1  offset   ○   ○  ○  元の型(内部表現)  PTR   ○   ○  ×  記憶番地  ENUM   ○   ○  ×  元の型(順序数) (10)副プログラム  副プログラムは、先に述べたように、 bool 返却値の型> (##9) という形でその型を表す。型Tの副プログラムへのポインタは、一般のポイン タと同じく、 で表す。副プログラムの結果の型は返却値の型である。  副プログラム呼び出しでは、引数は値渡しであるとする。参照渡しとする引 数は、そのオブジェクトの記憶番地をaddr演算子で求めたものを実引数とし て渡す。詳しくは副プログラム参照の節も参照されたい(##24)。 (##11) (11)代入文  代入文では、第1オペランドはオブジェクトを表す左辺値であり、第2オペ ランドの値を左辺値のオブジェクトの値として代入する。assignの両オペラ ンドの型は同じであり、assignのノードの型もそのオペランドと(型修飾子 の違いを除いては)同じ型とする。第1オペランドと第2オペランドが同じ型 の構造体または共用体であれば、第2オペランドの内容が全部まとめて第1オ ペランドの値として設定される(##14)。型修飾子constを持つ型を代入文の左 辺とすることはできない。第1オペランドと違う型の値を代入しようとすると きは、HIR-baseを生成するときに第2オペランドを第1オペランドの型に合 うよう明示的に変換しなければならない。ただし、null(NullNode)は任意の 型のポインタに代入できる(##10)。  代入文のオペランドの型としては、void以外のスカラ型または集成体型が 許される。すなわち、整数型、文字型、浮動小数点型、bool型、offset型、 ポインタ型、配列、列挙型、構造体、共用体を表す式の値を、それらと同じ型 を表す左辺値に代入することができる。 (##24)  注:配列の代入と関数値として配列を返すことについては、現在はまだ実現 できていない部分がある。 (##24) (12)制御文  if文、jump文、ループ文、値を返さないreturn文、switch文の結果の型 はvoidである。値を返すreturn文の型はその値の型と同じである。副プログ ラム呼出の型は副プログラムの返却値の型と同じである。 (##24) (13)その他の文  式文(ExpStmt)の型はオペランドである式の型と同じである。ブロック文の 型はvoidである。 (##24) (14)ラベル  ラベルを表す記号は、ラベル定義とラベル参照で使われるのみであり、演算 の対象とはならない。ラベル定義とラベル参照の型はvoidである(##9)。 (15)その他の表現  何もないことを表すnull (NullNode)はvoid型とする。 配列の要素数の要求される場所にnullが書かれているというように、nullに 限っては、void型であっても、HIRの仕様で指定されていれば、ある型Tの要 求されるオペランドとして書くことができる(##9)。  その他、上記で説明されていないHIR表現の型もvoidである。 2.3 型変換  (##21)  算術式等においては、オペランドの型を結果の型に合わせなければならない が、そのための型変換にconv演算子を使う。どのような場合にどのように型 変換するかは、入力言語の仕様によって決まるが、その変換はHIR-baseに変 換する際に行い、HIR-baseでは暗黙の型変換は行わない。 (##21)  整数型の型の間には、short int < int < long int < long long int とい う格付け(rank)があり、格の低い整数型から高い整数型へ変換する格上げ (integer promotion)のときは情報の欠損は生じない。浮動小数点型の型の 間にも、float < double < long double という格付けがあり、格の低い型か ら高い型へ変換する格上げのときは情報の欠損は生じない。  bool型を整数型に変換するときは、falseは0、trueは1となる。charは その文字コードを表す整数型に変換できる。enumの列挙子もそれに対応する 順序数を表す整数型に変換できる。列挙子を文字に変換するときは、その順序 数を文字コードとする文字に変換する。整数型と浮動小数点型の間でも明示的 な変換によって変換できる。  offsetの値はsigned intまたはsigned long intと見なすことができる。 この場合、「見なすことができる」ということは、(2進数による)内部表現値 が、convによる変換の前後で同じであることを意味する(##9)。  ポインタの値は記憶番地(address)を表し、それはsigned int, signed long int, unsigned int, またはunsigned long intと見なすことができる (##25)。符号付きか符号なしかは対象機種に依存するが、指定がなければ符号 なしとみなす。  一般に、算術演算等でオペランドの型を合わせるときは、格の上の方の型に 合わせるよう変換するが、入力言語の文法による変換規則が優先する。すなわ ち、CからHIR-baseに変換するときは、Cの言語仕様に従って、オペランドの 型を合わせるなどの変換を明示的に行なわなければなければならない。  convは基本型の間での実質的変換を行なうばかりでなく、型T1を指すポイ ンタpを型T2を指すポインタとみなすというような、見方を変えるための変 換にも (conv (PTR T2) (p (PTR T1)) ) のように使う(##24)。  conv 演算子により、型 Ts のオペランド x を型 Tr に変換して結果 r を 得るときの処理について、次にまとめて記す。bool については enum と同じ 規則が適用される(##24)。変換の具体内容は処理系定義である。この表では、 intは整数型を代表して表すとし、整数型の間の変換については省略している (##25)。 Operand x Result r Conversion operation ------- -------- -------------------- int char The character code of r is the value of x. enum The order number of r is the value of x. offset The value of r is the value of x. pointer The value of r is the value of x. (##25) char int The value of r is the character code of x. enum The order number of r is the character code of x. offset Same as (conv offset (conv int (x char))). enum int The value of r is the order number of x. enum The order number of r is the order number of x. offset Same as (conv offset (conv int (x Ts))). offset int The value of r is the value of x.. enum The order number of r is the value of x. pointer If the value of pointer is signed (##25) then the value of r is the value of x. ; If the value of pointer is unsigend (##25) then the value of r is signed-to-unsigned ; converted value of x. pointer int The value of r is the address represented ; by x. (##25) enum The order number of r is the value of x.(##25) offset If address is signed then the value of r is the value of x. ; If address is unsigend then the value of r is unsigned-to-signed converted value of x. pointer Address represented by r is the same to the address represented by x. Whether the internal representation of the objects pointed by x can be treated as an ; object pointed by r or not is programmer's responsibility. [[ (1) 暗黙の型変換  すでに述べたように、入力プログラムにおける暗黙の型変換は、HIR-base では、すべて明示的な型変換として表現する。HIR-Cの式には暗黙の型変換が 隠されていることがあるが、HIR-baseでは、それをCの言語仕様とHIR-base の規定に従って、すべて明示的に変換した形で表現する。例えば、charや boolはHIR-Cではintと同じ扱いであるが、HIR-baseではそれらはintとは 異なる型としているので、オペランドの型を合わせるために、必要に応じて明 示的な型変換をして表す。 C int a, plus; plus = a > 0; HIR-C (assign int (compGt int ) ) HIR-base (assign int (conv int (compGt bool ) ) ) (2) HIR-baseとHIR-Cの表現の違い  HIR-baseでは、char, bool, enumはint型とは異なる型として扱うが、 HIR-Cでは、Cと同じく、それらを整数型の一種として扱う。また、HIR-Cで は、Cと同じく、ポインタに対する整数の加減算とポインタの減算を許すが、 HIR-baseでは、整数の加減算はポインタを整数型に変換したとして加減算す る形とし、ポインタの減算はoffsetに変換したとして減算を行なう形をとる (##25)。 (3) 算術式  Cでchar, unsigned char, bool, enumと宣言してある変数や関数の値の型 は、HIR-Cでは整数型の一種として、算術演算のオペランドとして使えるが、 HIR-baseでは、それらを算術演算やintへの代入など、整数型の要求される 文脈で使うときは、int, unsigned intへの明示的な型変換を行う。逆に、 int, unsigned intからcharやbool, enumの変数への代入など、HIR-baseで 整数型をchar, unsigned char, bool, enumの要求される文脈で使うときは、 HIR-baseの制約に合うよう、明示的な型変換を行なう。 例 C char a, b; a = 'a'; b = a + 1; HIR-C (assign char (conv char ) ) (assign char (conv char (add int (conv int ) ) ) HIR-base (assign char ) (assign char (conv char (add int (conv int ) ) ) ) (4) ポインタ式  HIR-Cでは、Cと同じく、ポインタに対する演算が許される。p, qを型Tを 指すポインタとし、i, j, kを整数型とするとき、HIR-CとHIR-baseの間では 次のような表現変換をする。(以下の式で、p, qが単純変数でなく一般的なポ インタ式であれば、 p>や q>がその式を表す表現に 置き換わる。) C p + i HIR-C (add p> ) HIR-base (add p> (mult offset sはsizeof T ))   p-i に対する表現は、上でaddをsubに置き換えたものである。   ここで、sizeof T は一般にはoffset型の定数として表される(##9)。 C p - q HIR-C (offset offset p2> p1>) HIR-base (div int (sub offset p2> p1>) )) sはsizeof T ]] 2.4 初期値指定 (##14)  変数の初期値を指定する文   (setData 左辺値式 値指定) は、左辺値式で示される記憶場所に値指定で指定される値を設定することを表 わす。その左辺値式は左辺値を表わす定数式であり、値指定は定数か値指定の リスト、あるいは値指定の繰り返しを表わすものである。   値指定 -> 定数|(expList 値指定 値指定 ・・・ )       |(expRepeat 値指定 反復回数)  左辺値式がスカラー変数で値指定がその型に適合する型を持つ定数であれば、 その定数をそのスカラー変数の値として設定する。  (##24)   (expList 値指定 値指定 ・・・ ) は値指定を任意個数並べたリストであり、   (expRepeat 値指定 反復回数) は値指定を反復回数(整数定数)で示される回数だけ繰り返して並べたリスト を表わす。ここで、左辺値式がn個の要素をもつベクトルaで、値指定がn個 の要素を持つリストを表現するならば、上記はaの第i要素a[i]と値指定の 第i要素v_iに対して   ( setData a[i] v_i ) とすることをすべての要素に対して繰り返すことを表わす。左辺値式 がn個 の要素をもつ構造体sで、値指定がn個の要素を持つリストを表現するならば、 上記はsの第i要素s_iと値指定の第i要素v_iに対して   ( setData s_i v_i ) とすることを構造体のすべての要素に対して繰り返すことを表わす。  初期値設定文(setData ... )は、プログラムの実行開始に先立って設定する 値を指定するものであり、プログラム上のある点(副プログラム入り口等)に 到達したときに設定する値を指定するのは、代入文ないしループ文として表現 する。 (##24) 2.5 副プログラム参照 (##14)  副プログラム参照は   ( call 副プログラム式 引数リスト ) という形をとるが、副プログラム式は副プログラムへのポインタであり、引数 リストは式のリストまたは空なリストである。たとえばdoubleの引数xに対 する平方根をdoubleで求める関数sqrtの呼び出しは (call double (addr false double>> false double> sqrt> ) (list ) ) と表わされる。引数を持たない場合の引数リストは空なリストである。  引数として右辺値を持つスカラ式を指定すると、その値が副プログラムへ渡 される。ポインタを指定するとポインタの値が渡される。  配列の渡し方としては、    配列の先頭要素へのポインタを渡す    配列へのポインタを渡す    配列そのものを渡す(渡す側または受け取る側でコピーして受け渡す) の3種類があるが、HIRを生成するとき、入力言語の仕様に従ってHIRにおけ る渡し方を決める。配列の先頭要素へのポインタとして渡されたものを配列へ のポインタとして受け取ることや、逆に配列へのポインタとして渡されたもの を配列の先頭要素へのポインタとして受け取る場合の意味付けは、処理系定義 とする。その扱いは入力言語によって統一的に定めることが望ましい。  構造体や共用体の渡し方としては    構造体や共用体へのポインタを渡す    構造体や共用体そのものを渡す(渡す側または受け取る側でコピーして 受け渡す) という方法があり、HIRを生成するとき、入力言語の仕様に従ってHIRにおけ る渡し方を決める。ポインタを渡す場合はcall-by-referenceであり、構造体 や共用体そのものを渡すのはcall-by-valueである。  副プログラムの返却値の型が構造体や共用体であれば、returnのオペラン ドとして構造体や共用体を表わす式を指定する。その場合、返却値としては、 指定された構造体や共用体の値が返される。集成体を返すとき、それを入れる 記憶場所へのポインタを副プログラムへ渡し、副プログラムの中で渡された記 憶場所へ値を書き込む形態もとれるが、それに伴う処理はHIRで明示的に表現 しなければならない(##24)。  注:配列へのポインタではなく、配列自体を引数として渡すこと、配列自体 を返却値として受け取ることについては、まだ一部実現されていない部分があ る。 (##24) [[ Cにおける関数呼び出しの例 int func( int a[3], int i ){ return a[i]; } .... r = func(aa, 2); .... に対するHIRは、2.2節で述べた条件を考慮の上(##24)、次のように表現し てもよい。ただし、すでに述べたように、この例では言語仕様上はaはintへ のポインタを表すものであって、配列としての要素数が3であることを保証は しないことに留意する必要がある(##25)。 (subpDef void int > false int> func> // Initailization part. (labeldSt void (list ) // Label generated by subpDefinition of HIR. (block void (return int (subs int (undecay a> ) )))) // (##22) .... (assign int (call int (addr int > false int>> int > false int> func>) (list (decay aa>) ))) ]] [[ (##21) Fortranでは、実引数の番地を呼ばれ側に渡すが、それをそのままcall-by- referenceとして使う方法と、書き写して書き戻す方法とがある。スカラ変数 はその番地を渡し、配列は配列へのポインタを渡す。受け取る方では仮引数を 局所変数に複写して使ってもよいが、その値を変更した場合は、returnの前 に呼び出し側のポイント先に書き戻す必要がある。言語仕様では、同じ手続き の2つの実引数に重なりがある場合、その重なり合った部分に呼ばれた手続き で書き込んではいけないとしているので、両者は同じ結果をもたらすはずであ る。重なりの有無をコンパイル時に検出するとすれば、それはフロント部で行 うものとする。配列へのポインタとして受け取った仮引数は、配列へundecay して、subsによりその要素へアクセスする。たとえば、 PROGRAM SIMPLE INTEGER AA(10), BB(3, 5), CC, DD .... DD = F(AA, BB, CC) .... END FUNCTION F( A, B, C ) INTEGER A(10), B(3, 5) INTEGER I, X .... X = C + A(I) B(2, 3) = X C = C + 1 RETURN X END に対しては、 (prog // PROGRAM SIMPLE // program name // initialization part (subpDef void false int> SIMPLE_MAIN> ; // generated subprogram name // initialization part (labeldSt void (list ) // Label generated by subpDefinition of HIR. (block // subprogram body .... (assign int // DD = F(AA, BB, CC) (call int (addr > >> > false int> > >> > false int> F>) (list // argument list (addr > // Change to array pointer. AA>) (addr >> > BB>) (addr // Change to scalar pointer. )))) .... (return void ) ))) (subpDef void > >> > false int> F> // Initialization part (labeldSt void (list ) // Label generated by subpDefinition of HIR. (block void (assign int // Copy the contents of the pointed argument ; // to _var1. (contents int C>)) (assign int // (##25) (add int (subs int (contents // (##22) > A>) ))) (assign int (subs int (subs (contents > // (##22) >> B>) ) ) ) (assign int (add int )) (assign int // Copy back _var1 to the pointed argument. (contents int C>) ) (return int ) ))) ) のようなHIRを生成すればよい。Cではcallの第1引数を副プログラムへの ポインタとするが、Fortranではそれを副プログラム記号とするか副プログラ ムへのポインタとするかは、処理系定義である(##25)。  定数を引数として渡すときは、定数の番地を渡すよりも、定数を値とする一 時変数を生成して、その番地を渡す方がよい。そうすれば、定数引数の値を書 き換えるというプログラム誤りの悪影響を防止できる。  上記は1つの実現方式であって、言語仕様に合った首尾一貫したやり方であ れば、そのほかの方法でも一向に差し支えない。 ]] (##21) ---------- 削除した項目ないし用語  voidの値比較 (##25)  address (残存部分削除) (##25)  純粋文字列は文字列本体に置き換え (##25)  添字のindex表現 (##21)  cast演算子 (##10)