ここでは、組込み向けマシンのTMD(マシン記述)のいくつかの実装について説明する。
実装されたtmdファイルはsrc/coins/backend/genディレクトリに入っている。
ARMのマシン記述は組み込み向けに強化したバックエンドの新機能を使用す ることで TMD の記述量が減少することを検証するために実装したものである。
実装したARMのマシン記述ファイルは、arm.tmdという名前でCOINSのソースディ レクトリsrc/coins/backend/genの中に置いてある。
-coins:target=arm
組み込み用プロセッサによくある近接アドレスによる定数(リテラル)のロー ドや到達範囲の限られた分岐命令を簡単に利用できるように、バックエンドに リテラルとブランチの変換を追加した。このフィルタの動作を検証するために 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
-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のマシン記述はマルチコアの機種に対するTMDを実装したもので ある。命令パターン指定とコスト指定の多様性 (実行速度とコードサイズのコ スト) に対応する拡張機能を使用した記述を行っている。
実装したMicroBlazeのマシン記述ファイルは、mb.tmdという名前でCOINSのソースディレクトリsrc/coins/backend/genの中に置いてある。
MicroBlaze はソフトコア CPU のため、合成時のオプション設定で一部の 機能を制限することができる。
デフォルトでは MicroBlaze の全機能を有効にすることを想定したコード 生成を行う。すなわち以下の機能を使用するコードを生成する。
-coins:x-mb=fooで使用しない命令を指定できる。このfooでは以下の指定ができる。
-coins:optspaceかを指定することによって違う命令が生成される。
1ビットシフトについては、ALU による左シフト、1 ビットシフトユニット による右シフトが最速・最短 (1サイクル, 4バイト) であるので、add 命令あ るいは1ビット右シフトの命令が生成される。(MicroBlaze は1ビット左シフト 命令を持たない。)
2以上の定数nによるnビットのシフトは以下のようになる。MicroBlazeのマシン記述は OS の存在しない組み込み環境を想定している ため、すべての場合に通用する一般的な使用方法というものは記述できない。
そのため、ここでは Xilinx社のEDK(Embedded Development Kit)に含ま れている mb-gdb のソフトウェアシミュレータ上で COINS のテストプログラ ム集を実行するための手順を簡単に記述する。
なおコンパイル時に以下のようにオプションを指定することでMicroBlazeマシン記述によるコード生成が行われる。
-coins:target=mb
#!/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
#!/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
#!/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
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);