前節までの課題を済ませると,MiniMLコンパイラはひととおり完成する.MiniMLコンパイラのユーザインタフェースで説明したように「./minimlc -o tmp.s」と実行すれば,アセンブリファイルtmp.sが生成されるはずである.

アセンブリファイルから実行可能ファイルを生成するには既存のアセンブラを用いるが,OSとのインタフェースに関し,以下の点を考慮する必要がある.

  • OSからの_toplevel関数の呼出し
  • OSへのヒープ中のメモリ領域割り当ての要求

Minimlコンパイラの生成するアセンブリコードの中でこれらへ直接対処することも可能だが,そのためにはOSに関する深い知識が必要となる.OSはこの実験のカバーする範囲ではないため,その代わりに,OSとの橋渡しとして以下のC言語で書かれたライブラリ(lib/main.c)を用いることにする.

lib/main.c

#include <stdio.h>
#include <stdlib.h>

extern int _toplevel();

void *mymalloc(int n) {  /* <= 1 */
  return malloc(n * sizeof(int));
}

int main() {
  printf("%d\n", _toplevel());
  return 0;
}

上記2点をどのように処理しているかは一目瞭然であろう.前節で仮想機械命令のnewを変換する際,変換後のアセンブリコードから呼び出していたmymalloc関数が/* <= 1 */行目で定義されている.

tmp.sから実行可能ファイルを生成するには,Raspberry Pi 3上のCコンパイラgccを使って以下のコマンドを実行する.

$ gcc -o tmp lib/main.c tmp.s
$ ./tmp

さらに,MiniMLプログラムの実行自体に必要なわけではないが,プログラムの任意の区間の実行にかかる時間を計測するための簡単なライブラリlib/timer.cを用意している.

lib/timer.c

#include <stdio.h>
#include <time.h>

struct timespec start_ts, end_ts;

void start_timer() {
  // clock_gettime(CLOCK_REALTIME, &start_ts);
  clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_ts);
}

void stop_timer() {
  // clock_gettime(CLOCK_REALTIME, &end_ts);
  clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_ts);

  printf("cpu time: ");
  if (end_ts.tv_nsec < start_ts.tv_nsec) {
    printf("%4ld.%09ld", end_ts.tv_sec - start_ts.tv_sec - 1,
           end_ts.tv_nsec + 1000000000 - start_ts.tv_nsec);
  } else {
    printf("%4ld.%09ld", end_ts.tv_sec - start_ts.tv_sec,
           end_ts.tv_nsec - start_ts.tv_nsec);
  }
  printf(" seconds\n");
}

ここで定義されている2つのC言語の関数start_timer()stop_timer()を呼び出すコードは,MiniMLコンパイラが生成したアセンブリファイルの中に手作業で追加することを想定している.

  ...
  bl  start_timer
  何らかの処理
  bl  stop_timer
  ...

のように2つのbl命令を追加すると,その間に挟まれた何らかの処理にかかった時間が「cpu time: XXXX.XXXX seconds」の形式で標準出力に表示される.

通常であれば,関数を呼び出す際には,a1,a2レジスタに含まれている呼出し側関数のパラメータを退避/復帰するコードが前後に必要となるが,手作業でそれらのコードまで追加するのは煩わしい.そこで,この2つの関数に限っては呼出し規約を守らず,a1,a2レジスタの退避/復帰は呼び出される側で行うように,Cコンパイラが生成したアセンブリファイルを修正している.修正したアセンブリファイルは,lib/timer.sとして配布コード中に含まれている.まとめると,MiniMLコンパイラが生成したtmp.sの実行時間を計測するには,tmp.sに上記のとおりの修正を手作業で行ってから:

$ gcc -o tmp lib/main.c lib/timer.s tmp.s

として実行可能ファイルをつくる必要がある.

課題10(任意) バックエンドの移植
ARMアセンブリコードではなくC言語コードへ変換するコード生成器を作成し,性能比較を行いなさい.