Compiler Construction Lecture 1/8

Compiler Construction Lecture 1/8

先週の復習

先週は、

について勉強したのであった。



宿題1

以下のCのstatementに対応するMicro-Cの中間木と生成される6809のコードに ついて考察せよ。ただし、以下のint *a; という型を仮定する。

宿題2

&a = (int *)3; というのは、Micro-C ではエラーになる。なぜ、エラー なのかを説明せよ。

indirect and reference

Cでは、以下のポインタ演算が可能である。

これらは、6809のindex registerに直接対応している。例えば &p は 変数pのアドレスを指すindex registerを生成する。*p は、その index register の指し示す先を、load したり store したりするわけである。 これが構文解析される部分は以下のようになっている。
  1183          case MUL:
  1184                  getsym();
  1185                  e=rvalue(expr13());
  1186                  return(indop(e));
  1187          case BAND:
  1188                  getsym();
  1189                  switch(car(e=expr13()))
  1190                  {case INDIRECT:
  1191                          e=cadr(e);
  1192                          break;
  1193                  case GVAR:
  1194                  case LVAR:
  1195                          e=list2(ADDRESS,e);
  1196                          break;
  1197                  case FNAME:
  1198                          return e;
  1199                  default:error(LVERR);
  1200                  }
  1201                  type=list2(POINTER,type);
  1202                  return e;
indop()では以下のように中間木を生成する。
  1368	indop(e)
  1369	int e;
  1370	{	if(type!=INT&&type!=UNSIGNED)
  1371			if(car(type)==POINTER) type=cadr(type);
  1372			else error(TYERR);
  1373		else type= CHAR;
  1374		if(car(e)==ADDRESS) return(cadr(e));
  1375		return(list2(INDIRECT,e));
  1376	}



array and struct

array と struct は、アドレスを考えれば、統一的に扱うことができる。 例えば、a[4]というのは、a+4 というアドレスだし、a->type というのは、 a の指し示す構造体ののアドレスにtypeというoffsetを足したアドレス である。実際に、expr16()では、indop()を使ってarrayとstructの 中間木を作っている。Cから見た時には、すべてはアドレスと、その 指し示す先の大きさでしかない。大きさは型から決まる。

  1312  expr16(e1)
  1313  int e1;
  1314  {int e2,t;
  1315          while(1)
  1316                  if(sym==LBRA)
  1317                  {       e1=rvalue(e1);
  1318                          t=type;
  1319                          getsym();
  1320                          e2=rvalue(expr0());
  1321                          checksym(RBRA);
  1322                          e1=binop(ADD,e1,e2,t,type);
  1323                          e1=indop(e1);
  1324                  }
  1325                  else if(sym==LPAR) e1=expr15(e1);
  1326                  else if(sym==PERIOD) e1=strop(e1);
  1327                  else if(sym==ARROW) e1=strop(indop(rvalue(e1)));
  1328                  else break;
  1329          if(car(e1)==FNAME) type=list2(POINTER,type);
  1330          return e1;
  1331  }
  .....

  1377  strop(e)
  1378  {       getsym();
  1379          if (sym!=IDENT||nptr->sc!=FIELD) error(TYERR);
  1380          if (integral(type)||car(type)!=STRUCT && car(type)!=UNION)
  1381                  e=rvalue(e);
  1382          type = nptr->ty;
  1383          switch(car(e))
  1384          {case GVAR:
  1385          case LVAR:
  1386                  e=list2(car(e),cadr(e) + nptr->dsp);
  1387                  break;
  1388          case INDIRECT:
  1389                  if(!nptr->dsp) break;
  1390                  e=list2(INDIRECT,list3(ADD,cadr(e),list2(CONST,nptr->dsp
)));
  1391                  break;
  1392          default:
  1393                  e=list2(INDIRECT,list3(ADD,e,list2(CONST,nptr->dsp)));
  1394          }
  1395          getsym();
  1396          return e;
  1397  }



ポインタに関するコード生成

これらの中間木はgexprによってコード生成される。

  1663  gexpr(e1)
  1664  int e1;
  1665  {int e2,e3;
  1666          if (chk) return;
  1667          e2 = cadr(e1);
  1668          switch (car(e1))
  1669          {case GVAR:
  1670                  leaxy(e2);
  1671                  return;
  1672          case RGVAR:
  1673                  lddy(e2);
  1674                  return;
  1675          case CRGVAR:
  1676                  ldby(e2);
  1677                  sex();
  1678                  return;
      ....
  1829          case ASS: case CASS:
  1830                  assign(e1);
  1831                  return;
  1832          case ASSOP: case CASSOP:
  1833                  assop(e1);
  1834                  return;
GVARがleft value、RGVARがright valuewを表している。代入文は、 さらに、assign()というコード生成手続きを呼び出す。
  2014  assign(e1)
  2015  int e1;
  2016  {char *op;
  2017  int e2,e3,e4,e5,l;
  2018          op = (car(e1) == CASS ? "STB" : "STD");
  2019          e3 = cadr(e2 = cadr(e1));
  2020          e4 = caddr(e1);
  2021          switch(car(e2))
  2022          {case GVAR: case LVAR:
  2023                  gexpr(e4);
  2024                  index(op,e2);
  2025                  return;
  2026          case INDIRECT:
  2027                  switch(car(e3))
  2028                  {case RGVAR: case RLVAR:
  2029                          gexpr(e4);
  2030                          indir(op,e3);
  2031                          return;
ここで、index()がSTBやSTDを生成していることが分かる。indir()では、 LDB [,X] なdが生成されて6809のindirect addressing を有効に使用 している。

binop(), assign() などは、さらに細かい処理を行っているので、 このあたりを読んで見るとcompilerの構文木の生成と、コード生成の 実際が良く分かるはずである。特にアドレスの足し算、引き算では、 特別な扱いをしていることに注意しよう。

去年の問題の解答

以下のCのstatementに対応するMicro-Cの中間木と生成される6809のコードに ついて考察せよ。ただし、以下のint *a; という型を仮定する。

答え

もちろん、生成されるコードはコンパイルすれば簡単にわかってしまう。 これができなかった人は、さぼっているだけだろう。また、中間木に 関しても、それを印刷するコンパイラを作成したので答を得るだけならば 誰にでもできる。実際、/usr/open/lectures/kono/compiler/mc/mc1 を 使うと、(source を list 中に含める -s option を使って) 以下のように c.out が出力される。
* int *a;
a	EQU	0
* main()
* {
* 
* *a = 1;
main
	PSHS	U
	LEAU	,S
* generate code on type:
* list(INT)
* expr:
*  list(ASS,
*  list(INDIRECT,
*   list(RGVAR,0)),
*  list(CONST,1))
	LDD	#1
	STD	[0,Y]
* a = &a[3];
* generate code on type:
*  list(POINTER,
* list(INT))
* expr:
*  list(ASS,
*  list(GVAR,0),
*  list(ADD,
*   list(RGVAR,0),
*   list(CONST,6)))
	LDD	0,Y
	ADDD	#6
	STD	0,Y
* *a = *(a + a[3] + 2);
* generate code on type:
* list(INT)
* expr:
*  list(ASS,
*  list(INDIRECT,
*   list(RGVAR,0)),
*  list(RINDIRECT,
*   list(ADD,
*    list(ADD,
*     list(MUL,
*      list(RINDIRECT,
*       list(ADD,
*        list(RGVAR,0),
*        list(CONST,6))),
*      list(CONST,2)),
*     list(RGVAR,0)),
*    list(CONST,4))))
	LDX	0,Y
	LDD	6,X
	ASLB
	ROLA
	ADDD	0,Y
	PSHS	D
	LDD	#4
	PULS	X
	LDD	D,X
	STD	[0,Y]
* a = *( &a  + 3);
* generate code on type:
*  list(POINTER,
* list(INT))
* expr:
*  list(ASS,
*  list(GVAR,0),
*  list(RGVAR,6))
	LDD	6,Y
	STD	0,Y
どうして、このようなコード出力になるのかは木構造が分かってしまえば、 gexpr() をトレースするだけの問題である。この木構造は、最後の例では 単純な構文木 とは少々異なる。それは、indirect()や、rvalue() などが構文木を変換 しているからである。



問題2

&a = (int *)3; というのは、Micro-C ではエラーになる。なぜ、エラー なのかを説明せよ。

答え

実際にコンパイラを走らせてみれば、

    9:Lvalue required.
&a = (int *)3; 
     ^
というエラー出力される。このエラーメッセージを確認しないで答えを出そうと言う のは、少し虫が良すぎる。コードの(int *)3の部分にはエラーはない。実際、
* a = (int *)3; 
* generate code on type:
*  list(POINTER,
* list(INT))
* expr:
*  list(ASS,
*  list(GVAR,0),
*  list(CONST,3))
        LDD     #3
        STD     0,Y
という形にコンパイルされる。問題は明らかに&aの部分にある。しかし、 前の授業でも説明したように、構文的なエラーはない。このエラーは代入文 (assignemnt)に特有なエラーである。実際、Lvalue required というエラー は、
   257			(n==LVERR) ? "Lvalue required" :
であり、LVERR は、expr13の&の部分と、
  1363	lcheck(e)
  1364	int e;
  1365	{	if(!scalar(type)||car(e)!=GVAR&&car(e)!=LVAR&&car(e)!=INDIRECT)
  1366			error(LVERR);
  1367	}
に出てくる。expr13()ではなくlcheck()でエラーになっているのはトレース してみればすぐにわかる。この場合は、
   994  expr1()
   995  {int e1,e2,t,op;
   996          e1=expr2();
   997          switch (sym)
   998          {case ASS:
   999                  lcheck(e1);
  1000                  t=type;
ここのlcheck()でエラーになるわけである。実際、lcheck では、左辺値は、 整数(scalar)か、大域変数(GVAR)か、局所変数(LVAR)か、間接参照であるとし ている。&a は、そのどれでもないのでエラーとなる。(ANSI-C では、 この他にも、いくつか左辺値を許している。どのようなものがあるだろうか?)

今週は、宿題はありません。