マシン記述の実装例

ここでは、組込み向けマシンのTMD(マシン記述)のいくつかの実装について説明する。

実装されたtmdファイルはsrc/coins/backend/genディレクトリに入っている。

ARMのマシン記述の実装

ARMのマシン記述は組み込み向けに強化したバックエンドの新機能を使用す ることで TMD の記述量が減少することを検証するために実装したものである。

実装したARMのマシン記述ファイルは、arm.tmdという名前でCOINSのソースディ レクトリsrc/coins/backend/genの中に置いてある。

仕様

ARM Linux 用の gcc と互換性を取るという方針で実装した。ただし gcc はい ろいろなオプションを持っているので完全に互換なわけではない。なおターゲッ トは ARM Linux であるから hosted 環境(OS を持つ環境)となる。したがっ て特別な作業を行わずともターゲット上に用意されている各種のライブラリを 使用することができる。
なお浮動小数演算は FPU 命令を生成するが、実際には ARM Linux カーネルに よるソフトウェアエミュレーションで実行される。(gcc の hard float と同 等)

使用方法

ターゲットは ARM Linux であるから、基本的には通常の PC 用 Linux での使 用方法と同じである。異なる点はコンパイル・リンクして得られた実行ファイ ルを実行するさいに、ファイルをターゲットに転送する必要があることくらい である。 なおコンパイル時に以下のようにオプションを指定することで ARM マシン記 述によるコード生成が行われる。
 -coins:target=arm

THUMBのマシン記述の実装

組み込み用プロセッサによくある近接アドレスによる定数(リテラル)のロー ドや到達範囲の限られた分岐命令を簡単に利用できるように、バックエンドに リテラルとブランチの変換を追加した。このフィルタの動作を検証するために ARMよりも制限の強いThumb命令セットのマシン記述を実装した。

実装したTHUMBのマシン記述ファイルは、thumb.tmdという名前でCOINSのソー スディレクトリsrc/coins/backend/genの中に置いてある。

仕様

ターゲットは free standing 環境(組み込み環境で、OS を想定しない環 境)を想定している。したがってライブラリは一般に使用者が用意する必要が ある。 具体的な仕様は以下のとおりである。

使用方法

THUMBのマシン記述は OS の存在しない組み込み環境を想定しているため、 すべての場合に通用する一般的な使用方法というものは記述できない。

また THUMB を完全にサポートする OS は一般的ではないため、ここでは一 例として入手の容易な ARM 環境 (具体的には Armadillo9 のような ARM Linux 環境や Linux 上の ARM ソフトウェアエミュレータ) 上で THUMB コー ドを実行するための方法を記述する。

なおコンパイル時に以下のようにオプションを指定することで THUMB マシン記述によるコード生成が行われる。

 -coins:target=thumb
標準ライブラリの利用
ARM 環境のライブラリをTHUMBのコードから利用するためには gcc を使用して THUMB モードから呼び出すことのできるライブラリを作成する必要がある。そ の為には以下のオプションを指定してライブラリを作成する。
 -marm -mthumb-interwork
ランタイムライブラリ

THUMBには整数除算命令が存在しないため gcc のランタイムライブラリ (__divsi3, __modsi3等) を利用する。

ただし、これらはARMのサブルーチンであり、THUMBモードからbx命令で直 接呼び出した場合には正しくTHUMBモードに復帰することができない。そこで 以下のラッパを被せて使用する。


        .text
        .align  2
        .global wrapper__divsi3
wrapper__divsi3:
        str     %lr,[%sp,#-4]!
        bl      __divsi3
        ldr     %lr,[%sp],#4
        bx      lr
 
        .global wrapper__modsi3
wrapper__modsi3:
        str     %lr,[%sp,#-4]!
        bl      __modsi3
        ldr     %lr,[%sp],#4
        bx      lr
 
        .global wrapper__udivsi3
wrapper__udivsi3:
        str     %lr,[%sp,#-4]!
        bl      __udivsi3
        ldr     %lr,[%sp],#4
        bx      lr
 
        .global wrapper__umodsi3
wrapper__umodsi3:
        str     %lr,[%sp,#-4]!
        bl      __umodsi3
        ldr     %lr,[%sp],#4
        bx      lr
THUMBのマシン記述の生成コードはblx命令でこれらのラッパを呼び出す。

あるいは__divsi3などをCで記述し、リンクすることでもこの問題を回避す ることができる。ただし、もともとのルーチンと名前を同じにしないよう、 thumb__divsi3のようにthumbを前置する必要がある。

以下に例を示す。このファイルをTHUMBのマシン記述を使用してコンパイル することでTHUMBモードで使用可能なランタイムライブラリが得られる。


 /*
        div.c - runtime routine for thumb
 */
 
 static unsigned int main__udivsi3(unsigned int x, unsigned int y, unsigned int 
*p) /* x / y */
 {
        unsigned int z = 0;
        int i;
 
        for (i = 32; i != 0; i--) {
                z <<= 1;
                z |= (x >> 31);
                x <<= 1;
                if (z >= y) {
                        z -= y;
                        x++;
                }
        }
        *p = z;
        return x;
 }
 
 unsigned int thumb__udivsi3(unsigned int x, unsigned int y) /* x / y */
 {
        unsigned int z;
        return main__udivsi3(x, y, &z);
 }
 
 unsigned int thumb__umodsi3(unsigned int x, unsigned int y) /* x % y */
 {
        unsigned int z;
        main__udivsi3(x, y, &z);
        return z;
 }
 
 int thumb__divsi3(int x, int y)
 {
        int xs = x ^ y;
        x = thumb__udivsi3(x >= 0 ? x : -x, y >= 0 ? y : -y);
        if (xs < 0) {
                x = -x;
        }
        return x;
 }

 int thumb__modsi3(int x, int y)
 {
        int ys = x < 0;
        x = thumb__umodsi3(x >= 0 ? x : -x, y >= 0 ? y : -y);
        if (ys) {
                x = -x;
        }
        return x;
 }

MicroBlazeのマシン記述の実装

MicroBlazeのマシン記述はマルチコアの機種に対するTMDを実装したもので ある。命令パターン指定とコスト指定の多様性 (実行速度とコードサイズのコ スト) に対応する拡張機能を使用した記述を行っている。

実装したMicroBlazeのマシン記述ファイルは、mb.tmdという名前でCOINSのソースディレクトリsrc/coins/backend/genの中に置いてある。

実装方針

Xilinx社の提供する mb-gcc と互換性を取るという方針で実装した。ただし gcc はいろいろなオプションを持っているので完全に互換ではない。 なおターゲットは free standing 環境(組み込み環境で、OS を想定しない環境)を想定している。
命令セット

MicroBlaze はソフトコア CPU のため、合成時のオプション設定で一部の 機能を制限することができる。

デフォルトでは MicroBlaze の全機能を有効にすることを想定したコード 生成を行う。すなわち以下の機能を使用するコードを生成する。

ただし、オプションの指定でこれらの機能を使用しないコードを生成することもできる。
コード生成オプション
コンパイル時のオプション
-coins:x-mb=foo
で使用しない命令を指定できる。このfooでは以下の指定ができる。
speed と size のコスト
速度優先・スペース優先の指定 が出来るようになったので、以下のような命令に関してspeedとsizeのコストをtmdに 書き込んである。これらについては速度優先(デフォールト)かスペース優先
-coins:optspace
かを指定することによって違う命令が生成される。
シフトのコード生成

1ビットシフトについては、ALU による左シフト、1 ビットシフトユニット による右シフトが最速・最短 (1サイクル, 4バイト) であるので、add 命令あ るいは1ビット右シフトの命令が生成される。(MicroBlaze は1ビット左シフト 命令を持たない。)

2以上の定数nによるnビットのシフトは以下のようになる。
乗算のコード生成
2 倍は add命令が最速・最短 (1サイクル, 4バイト) であるのでそれを生成する。 それ以外の場合は乗算器が使えるか使えないかで異なる。乗算器による乗算は3サイクルで処理される。
その他の制限
以下の機能はサポートしていない。

使用方法

MicroBlazeのマシン記述は OS の存在しない組み込み環境を想定している ため、すべての場合に通用する一般的な使用方法というものは記述できない。

そのため、ここでは Xilinx社のEDK(Embedded Development Kit)に含ま れている mb-gdb のソフトウェアシミュレータ上で COINS のテストプログラ ム集を実行するための手順を簡単に記述する。

なおコンパイル時に以下のようにオプションを指定することでMicroBlazeマシン記述によるコード生成が行われる。

 -coins:target=mb
必要なもの
コンパイル・リンク方法
mbccc.sh
指定されたソースファイルをコンパイル・リンクする。(複数ファイルは処理できない。)
#!/bin/sh
# compile *one* C file and link it.

CP=./classes
TARGET=mb
LIB=/work/coins/src/lib.c

MBROOT=/var/tmp/mb/release/linux/microblaze/bin
CPP="$MBROOT/mb-gcc -E"
AS=$MBROOT/mb-as
LD=$MBROOT/mb-gcc
LFLAGS=-Zxl-no-libxil

TMPOUT=/tmp/`basename $1 .c`.s
OUT=`basename $1 .c`.mb-exe

rm -f $TMPOUT $OUT

java -cp $CP coins.driver.Driver -coins:target=mb,preprocessor="$CPP",x-mb=no-fpu \
    $2 -S -o $TMPOUT $1
$LD -msoft-float -fshort-double $LFLAGS \
    -Xlinker -defsym -Xlinker _STACK_SIZE=0x10000 \
    -Xlinker -Map -Xlinker a.map \
    $TMPOUT $LIB -o $OUT -lm_m_bs_sfps -lc_m_bs_sfps

実機で動かす場合には最後の部分が例えば以下のようになる。

java -cp $CP coins.driver.Driver -coins:target=mb,preprocessor="$CPP" \
    $2 -S -o $TMPOUT $1
$LD -mhard-float -fshort-double $LFLAGS \
    -Xlinker -defsym -Xlinker _TEXT_START_ADDR=0x24000000 \
    -Xlinker -defsym -Xlinker _STACK_SIZE=0x10000 \
    -Xlinker -Map -Xlinker a.map \
    $TMPOUT $LIB -o $OUT -lm_m_bs_hfps -lc_m_bs_hfps
mbgdb.sh
指定されたファイルをシミュレータ上で実行する。
#!/bin/sh

MBROOT=/var/tmp/mb/release/linux/microblaze/bin
MBGDB=$MBROOT/mb-gdb
ANS=_tmp.ans

rm -f $ANS

$MBGDB -nw $1 <<EOF | tee log.$1
target sim
load
b _program_clean
set print elements 0
run
bt
x /s &print_buffer__
quit
EOF

echo
echo -- result
grep print_buffer_ log.$1 | tr -d \" | sed -e 's/.*__>:  //' | \
    sed -e 's/\\n/\n/g' | sed -e 's/\\t/\t/g' > $ANS

実機で動かす場合には中間部分が例えば以下のようになる。

$MBGDB -nw $1 <<EOF | tee log.$1
target remote 192.168.1.1:1234
load
b _program_clean
set print elements 0
c
bt
x /s &print_buffer__
quit
EOF
mbtest.sh
指定されたファイルをコンパイル・リンク・実行し結果を正解と照合する。
#!/bin/sh

OUT=`basename $1 .c`.mb-exe
ANS=`dirname $1`/`basename $1 .c`.ans
BIN=/work/coins/bin
CMD=`dirname $1`/`basename $1 .c`.cmd

# skip test which read *.cmd
if [ -f $CMD ] ; then
  echo ::: excluded $1
  exit 0
fi

# skip long long
if grep "long long" $1 ; then
  echo ::: excluded $1
  exit 0
fi

# skip long double
if grep "long double" $1 ; then
  echo ::: excluded $1
  exit 0
fi

$BIN/mbccc.sh $1
$BIN/mbgdb.sh $OUT

if grep 'N/A' _tmp.ans; then
  echo ::: excluded $1
else
  if diff -Bc $ANS _tmp.ans ; then
    echo ::: passed $1
  else
    echo ::: failed $1
  fi
fi
lib.c
サポートファイル

mb-gdb のシミュレータ上には OS が存在しないため入出力を実行すること ができない。したがって COINS のテストプログラムを実行するには少し工夫 が必要である。以下のファイルをテストプログラムと一緒にリンクすることで COINS のテストプログラムの多くのものが実行可能になる。

#include <stdarg.h>

#ifdef __i386
int flag;
int _program_clean();
#endif

int __errno;

/* dummy allocator */
char space[1000];
char *top = space;

void *calloc(int n, int size)
{
    char *tmp = top;
    if (sizeof(space) - (top - space) < n * size) {
        printf("failed: calloc\n");
        exit(11);
    }
    top += n * size;
    return tmp;
}

void *malloc(int n)
{
    return calloc(n, 1);
}

/* output buffer */
char print_buffer__[30000];     /* tpprimed requires 24754bytes */
int print_i__ = 0;

int printf(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    print_i__ += vsprintf(&print_buffer__[print_i__], fmt, ap);
    // check buffer overflow.
    va_end(ap);
#ifdef __i386
    if (!flag) {
        atexit(_program_clean);
        flag = 1;
    }
#endif
}

int fprintf(void *fp, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    print_i__ += vsprintf(&print_buffer__[print_i__], fmt, ap);
    // check buffer overflow.
    va_end(ap);
#ifdef __i386
    if (!flag) {
        atexit(_program_clean);
        flag = 1;
    }
#endif
}

int putchar(int c)
{
    print_buffer__[print_i__++] = c;
#ifdef __i386
    if (!flag) {
        atexit(_program_clean);
        flag = 1;
    }
#endif
    return c;
}

int puts(char *s)
{
    char *tmp = s;
    while (s && *tmp) {
        print_buffer__[print_i__++] = *tmp++;
    }
    print_buffer__[print_i__++] = '\n';
#ifdef __i386
    if (!flag) {
        atexit(_program_clean);
        flag = 1;
    }
#endif
    return s;
}

void __assert(const char *f, int n, const char *s)
{
    printf("%s: %d: %s\n", f, n, s);
}

/* dummy handler */
int _program_clean() {}
int _program_init() {}
int _hw_exception_handler() {}
int _exception_handler() {}
int _interrupt_handler() {}

/* not supported function, exit */
#define die(name) \
int name() { printf("N/A: " # name "()\n"); exit(1); }

die(read);
die(write);
die(lseek);
die(getchar);
die(getc);
die(__srget);
die(scanf);