レジストしないと入手できないバイナリの一部としてしか AVR Toolchain のバイナリは存在しないようだ。
しかも全体が巨大になってきている。まだβだが、AVR Studio 5 なんて 500MB 超。さらに、avrtiny10 の対応がイマイチで、何度か入れ替えないといけなさそうな感じがする。
幸い、古そうなものの、パッチ集が見つかったので 自分でビルドしてみることにした。
ビルドできるようになれば、自作 AVR Core 対応も盛り込める可能性が出てくるし、やってみる価値はある。
Linux 専用なら問題ないだろうが、作りたいのは MinGW+MSYS 版。使っている環境はちょっと古いのでハードルが高いがまぁやってみよう。
今の環境は、以前 mips 用に gcc-4.2.4 を ビルドしようとしたことがあって、mpfr や gmp 、gettxt はすでに入っている。だが、パッチは gcc-4.4.3 用で gcc-4.4.3 では、make-3.80 を要求される。
以前試したときは、make-3.8x が動かなくて gcc-4.3 以降のビルドを断念した経緯がある。
準備
mpfr , gmp をスタティックリンクしているので、一応書く必要がある。
両方とも
./configure --prefix=/mingw
make; make install
これでインストールしている。使ったバージョンは、 mpfr-2.4.2 と gmp-4.3.2 だったり mpfr-2.4.1 と gmp-5.0.1 だったり。
gettext, libiconv については下記参照。
binutils
binutils のパッチは、binutils-2.20.1 に対するもの 。binutils-2.20.1 を入手しパッチを全部当てたものに
./configure --prefix=/c/AVR_Toolchain --target=avr --disable-shared \
--disable-threads --enable-languages=c,c++ --disable-dssi \
--disable-plugin
として ビルドを試みた。make doc できる環境がないので bfd でエラーになるが、bfd/Makefile を修正して先に進む。(doc po となっている所の doc を削除)
次に、ld でエラーになる。eavrtiny10.o がないという旨のエラー。これは、ld/Makefile.in に ルールが入っていないためと分かった。ちなみに ld/Makefile.am には入っているが、反映されない。
avrxmega7 と同じように avrtiny10 のルールを追加すれば OK だった。
これで ビルド完了。make install も OK 。
gcc
gcc のビルドで難儀していたのだが、ここに情報があった。
- --with-build-time-tools が,忘れがちですけど重要
(as や ld があるディレクトリを指定する模様) - make は、MSYSのバイナリを使え
とのこと。
ちなみに、atmel の avr-gcc に対して avr-gcc -v とすると それを作ったときの configure オプションが分かる。
make をインストールして
./configure --target=avr --prefix=/c/AVR_Toolchain --enable-languages=c,c++ \
--with-dwarf2 --disable-doc --disable-shared --disable-libada \
--disable-libssp --disable-nls --enable-fixed-point \
--with-build-time-tools=/c/AVR_Toolchain/avr/bin
とやって ビルドを試みた。
--enable-fixed-point したおかげで、巨大な libgcc.a が生成され時間もものすごくかかる。
さらに、ar でエラーになる。具体的には libgcc.a の生成が avr25 avr30 と成功して avvr31 で メモリ不足。(ヒープが取れない? Win32 error 487)
ググると
regtool -i set /HKLM/Software/Cygwin/heap_chunk_in_mb 1024
regtool -v list /HKLM/Software/Cygwin
という解決方法と rebase 使って
rebase -b 0x30000000 /c/AVR_Toolchain/bin/msys-1.0.dll
rebase -b 0x30000000 /c/AVR_Toolchain/avr/bin/msys-1.0.dll
とかする解決方法が見つかった。
regtool が見つからないし、見つかったとしても 使っている MSYS が 古いので不安がある。安全そうな後者を選んだ。だが、効果がないような? バイナリは出来ていて libgcc が作れないだけだしひどく時間がかかるので、linux 版からコピーすることに。
avr-libc
avr-libc のパッチは 1.7.0 に対するものが存在しているが、既に 1.7.1 が出ており、対応済みの模様。すなおに、1.7.1 を使う。
./configure --build=`config.guess` --host=avr --prefix=/c/AVR_Toolchain
configure はこう。
ビルド完了
これでビルドすることができた。.. が多々問題があることが分かり自製パッチを作成してバイナリを作ることにした。
出来たもの
- AVR_Toolchain-20110409-win.tar.gz -- Windows 版 (27MB)
- AVR_Toolchain-20110409-lin.tar.gz -- Windows 版 (25MB)
- AVR_Toolchain-20110409-src.tar.gz -- パッチ集
4/21 版 (アップデート) - AVR_Toolchain-20110421-src.tar.gz -- パッチ集
(バイナリ準備中)
展開すると AVR_Toolchain をトップディレクトリとして、展開される。これを 好きなところに 展開して (rename しても良い ) bin にパスを通せば OK 。
4/9 版
使ったパッチは、source ディレクトリに置いてある。あと Windows 版には 必要になった dll もいれてある。
- http://sourceforge.net/projects/mingw/files/MinGW/libiconv/libiconv-1.13/
- http://sourceforge.net/projects/mingw/files/MinGW/gettext/gettext-0.17-1/
dll のソースまで入れていない。元は上記から取ってきたもの。ひょっとしたら違う環境では足りない dll が出るかも。上記の Tree にあるはず。
ちなみに、lzma は、7-zip で解凍できる。
バイナリを使う人がいるかどうか分からないが、一応置いておく。
追記: avrtiny10 対応の分析
多くの人は興味がないだろうが、私に取っては最大の興味事。どんな風に対応しているのかちょっと見てみることにする。
まずは gcc 。avrtiny10 は、メインの パッチと 2 つの バグ修正パッチから成っている。
2441 54-gcc-4.4.3-avrtiny10.patch
43 55-gcc-4.4.3-avrtiny10-bug-12510.patch
48 62-gcc-4.4.3-avrtiny10-non-fixedpoint.patch
メインのパッチは、2441 行もあるが、その内訳は、
1680 gcc/config/avr/avr.c
98 gcc/config/avr/avr.h
31 gcc/config/avr/avr.md
443 gcc/config/avr/libgcc-fixed.S
160 gcc/config/avr/libgcc.S
29 gcc/config/avr/t-avr
md が少ないのは意外だが、プリミティブは avr.c に集約されている。
基本的に 元のコードを修正して attiny10 の場合はこう、そうでなければ元のコードという形が多い。だから純粋な修正量はパッチの 1/3 ぐらいに見積もれる。
libgcc-fixed.S は多いが、結局 disable にされていて使っていない。libgcc.S の修正は、sbiw を subi + sbci に直しているような所が多い。
__do_copy_data のところは、まるごと disable にされている。これでは初期値がコピーされないが、後でちゃんと対応するつもりだったのだろう。
良く見ると lpm Z+ 系のコードが(当然ながら)あって ld Z+ で同じようにできる。アドレスに 16384(0x4000) を加算するぐらいの変更で、対応コードを作れる。
さて、avr.c の修正を ちょっと見てみる。MAX_LD_OFFSET 周りが目立つ。 あとは、adiw/sbiw 対応とか。同じような修正が沢山入っているだけという印象がある。
感触としては、自分で直せそうな感じ。ただし、全然違う修正が必要になった場合だいぶ難儀しそう。
ところで、lds/sts 自体を変更しているところが見あたらない。どうも binutils の as で 対応することを期待しているように思える。
binutils にも
52-binutils-2.20.1-avrtiny10.patch
がある。内訳は次のようになっている。
11 bfd/archures.c
11 bfd/bfd-in2.h
15 bfd/cpu-avr.c
25 bfd/elf32-avr.c
50 gas/config/tc-avr.c
11 include/elf/avr.h
45 include/opcode/avr.h
22 ld/Makefile.am
0 ld/Makefile.in
12 ld/configure.tgt
16 ld/emulparams/avrtiny10.sh
ほとんどは、単に アーキテクチャの追加のためのコード。
lds/sts に対応するとすれば、include/opcode/avr.h になる。これには、なにやら修正は入っているのだが、対応は入っていない。
AVR_INSN (sts, "i,r", "1001001ddddd0000", 2, AVR_ISA_SRAM, 0x9200)
AVR_INSN (lds, "r,i", "1001000ddddd0000", 2, AVR_ISA_SRAM, 0x9000)
この部分だが、条件を変更しただけ。これよくよく見ると AVR_ISA_SRAM は、SRAM 持っているアーキテクチャの意味で 2word LDS/STS を持っていることになっている。
例えば、AVR_ISA_LDS1 と AVR_ISA_LDS2 を追加して、include/opcode/avr.h を定義しなおすことは比較的簡単そうだ。だが、それをパーズするコードが必要なわけで、gas/config/tc-avr.c と opcodes/avr-dis.c にコードを追加しなくてはならない。あと ldd/std と重なる部分があるから何か対応しないといけない。-- これはちょっと面倒そうなので、パス。
一応調べてみると bfd_mach_avrtiny10 というのがあって、ディスアセンブルするときに、
if (info->mach == bfd_mach_avrtiny10 )
のようにして判断可能らしい。gas の方は、in/out に近い処理をすることになるはず。
AVR_INSN (out, "P,r", "10111PPrrrrrPPPP", 1, AVR_ISA_1200, 0xb800)
AVR_INSN (in, "r,P", "10110PPdddddPPPP", 1, AVR_ISA_1200, 0xb000)
AVR_INSN (sts, "i,r", "10101AAAddddAAAA", 1, AVR_ISA_LDS1, 0xa800)
AVR_INSN (lds, "r,i", "10100AAAddddAAAA", 1, AVR_ISA_LDS1, 0xa000)
こんな風に定義しておいて、AAA.. の処理を PPP.. の処理を参考に作ることになる。
... と思ったらそんな甘いものではなかった。1 ワード lds/sts のアドレスは、リロケータブル前提だから、BFD_RELOC_16 のような リロケータブル情報が必要で、やってられない印象。rtavr は 2 word LDS/STS を標準にすることで 逃げておこう。
もうちょっと見てみたのだが、いっそのこと使わないというのが楽そう。そうすると ldi x 2 + ld/st になるはず。レジスタを消費するが、2word が 3word になるだけだ。それをするためには、MAX_LD_OFFSET を 0 にしてしまえば良さそう。
MAX_LD_OFFSET は、ldd とかの話で間違い。lds/sts は、gcc/config/avr/avr.c で 1/2/4 バイト用が定義されている。
ldi r28/ldi r29/ld X+/ld X+/... といった命令列に変換しないといけなさそう。 それは良いのだが、それを定義する方法がイマイチ分からない。勝手に X 使ったらまずそうだし。
あと、 1 ワード lds/sts に対応するとすれば、命令数のカウントの値を変えないといけなさそう。多くカウントする分にはバグにならないかも知れないが。
逆に rtavr 専用のコードにするには、 MAX_LD_OFFSET を大きくしてしまう。これで 2KB までシンボルを割り当てられる。ただ、rtavr 専用のコード を作っても生き残らないだろう。とりあえず lds/sts を使わない方向で考えておく。
そんなことより、自作 AVRコア用の 定義を入れてみたい。せっかくソースコードがあるんだし。attiny40 を パッチで grep して同じように変更すれば良い。
まずは名前を決めなくては。rtavr で attiny40 もどきだから -mrtavr40 あたり? ついでだから generic な rtavr も入れておこう。あと define も __RTAVR_rtavr40__ あたりにしておくか。
avr-libc も attiny40 をゼロから入れているパッチがあるから参考になる。... と思ったら対応してなかった。まぁ 1.7.1 で attiny40 を grep すれば なんとかなる。... はずが...
結構たいへん。変更するところがかなりある。configure とかも変更しないといけないし。それと crtXX と ioXX.h の名前を決めないといけない。元が crttn40.o と iotn40.h なので 対応したものが crtrt40.o と iort40.h ということにしておこう。
追記: ちょっと N-Queeen をビルドしてみる。
kxavr の作者の kwhr0 さんのところの N-Queen をビルドしようと やってみた。
そうしたら 2種類 のエラーが出た。
- relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
- libc/stdio/fputc.c:41: undefined reference to `__prologue_saves__'
ほかの AVR_Toolchain ではどうなのか気になったので as4e-ide-2.7.0.851-linux.gtk.x86.zip に含まれる AVR_Toolchain (AVR_8_bit_GNU_Toolchain_3.1.0_200) で試してみたところ、全く同じエラーだった。
ちなみに、 -S でアセンブラソースを作成してみると、出力コードもほぼ同じ。(自分で改造した 初期値のコピー関係だけわずかに違う)
このコードのサイズは、874バイト。attiny2313 にターゲットを変更すると 400 バイトにまで減る。
こういう問題があることは分かった。そして 自分でビルドした AVR_Toolchain は、AVR_8_bit_GNU_Toolchain_3.1.0_200 相当だということも分かった。
さて、なにげに ここ に as5-beta の ソースが公開されている。
ちょっと見てみたが、binutils のパッチは デバイスの追加があるが基本変更なし。avr-libc は 1.7.1 に対するもので、新デバイス追加と ちょっとしたバグFIX -- avrtiny10 は無関係。
肝心の gcc は、4.5.1 対応になってしまった。..が、ざっと見たところ単純移植のような印象。まだまだのようだ。
N-Queen の命令を調べてみた。
queen_r.c 自体はコンパイルできている。
tiny2313 190 命令
tiny40 427 命令
これだけの差になったのだが、tiny40 の命令を見てみると sbci/subi だけで 197 命令。
2 バイトの演算を置き換えているところが多かったからそれかとも思ったのだが、tiny2313 の adiw/sbiw は合計 3 、ldd/std は合計 10 。2 バイトの演算はこれぐらいしかないのに、
しょうがないので、実際のコード生成をみてみた。... ところ avrtiny10 なら XXX の命令列 そうでないなら元の命令列というコードの部分で 命令数を 計算して ポインタ経由で代入している。そして 命令数は変更していない。(as5_beta も同じ)
たぶんこれが、R_AVR_7_PCREL で引っかかる原因。カウントした命令数は 条件分岐とかの範囲の計算に使われているのだろう。... と思い直してみたところ ... 直った。
修正箇所が 80 ほどあった。手を付けたのを後悔するほどだったが、なんとか完遂。
コードを見ると 元の命令列と同じ動作になるような 命令列 を生成している。2 命令が 3 命令になる程度ならそれでも良いが ... 2 命令が 6 命令とか 6 命令が 12 命令とか ひどいものがある。しかも __zero_reg__ とか使っているし。効率が悪ければ使わない... みたいなことが出来ないのだろうか?
コード生成が XMEGA と それ意外に分かれているところがある。この両方に -- XMEGA の部分まで -- avrtiny10 なら XX というのが埋めこまれている。すぐ上の条件すら見ていない機械的変換 -- ひどいものだ。
まだひどいところがあった。インデックスレジスタに offset を足して、後でoffset を引いているようなところが多いのだが、12 命令も使っているものは 2 回に分けて subi/sbci している。本来なら 8 命令で済むものだった。 subi/sbci が多いのもたぶんこれが原因。
また、インデックスレジスタを多用することになるのは、レジスタが少ないからだろう。Y あたりをベースにして、offset 付きで ld/st しようとするわけだ。あと Y 自体を変更する場合 __temp_reg__ を使わざるを得ない。それは仕方ないとしても何故セーブ対象の r16 を使うのだろう? 理由は理解できていない。
それはともかく、込み入った C コードの命令効率はあまり良くならないことは分かった。当初 3割マシぐらいだと漠然と思っていたが、コードによって随分違う。込み入ったコードは 2 倍まで覚悟しないといけないようだ。
ちなみに __zero_reg__ は ZERO_REGNO_AVRTINY10(r17) に割り当てている。どうやって制御しているのかは知らない。
ここを 機械的変換している限り命令効率が悪い問題は解決しない。.. と言っても 2 倍程度。ブロックRAM は 4K あるので TINY2313 程度のことはできる。最大は 8KB あるから 足りなければ 2 倍にすれば良いという考え方もある。
ついでに 2 つめの問題を調べると... libgcc.S にある __prologue_saves__,__epilogue_restores__ が avrtiny10 では disable されていた。(as5_beta も同じ)
これは、どうやって直すのが正しいのだろうか? setjmp/longjmp のようなものなのだが、使って良いレジスタがよく分からないし、存在しないレジスタの分を空けるのかどうかも良くわからない。
gcc のコード生成をいじってみた。
いくつかコード生成をチューニング。
それに加えて LDS/STS を使わないコードにしてみたりしている。
適当なので、コンパイルだけはできるものの LDS/STS 周りはあまり期待できない。だが、元は 2word LDS/STS が出てしまうから attiny40 など実際のCPU では 動かないのだ。
期待できないといっても、気をつけるのは X (r26/r27) の競合のみ。たまたま動く場合もあるようだ。
rtavr では、2word LDS/STS をサポートしたから本当は いじらない方が安全。いつでも元のコードにできるよう ifdef で切り分けてはある。
その他の コード生成 は、命令数カウントを直した上で 注意深く変更したつもり。
メモしておくと
- XMEGA 用のコードの中にもある avrtiny10 対応コードを削除
- MAX_LD_OFFSET のところの avrtiny10 対応コード は ldd 命令を機械的に変換したものだと分かったので、avrtiny10 では削除。
- --R とコメントがあったところ、なにやら変なので素直なコード(ld -X/ld -X とか) に変更。
ちょっとやりすぎか。命令数のカウントのところだけにした方が信用できる。
これは、命令数のカウントのところだけ直した最初のもの。あと 、__prologue_saves__ などを修正した libgcc.S のパッチも入っているが、これまた適当なもの。変更する場所を示すのが目的と言っても良い程度。
__zero_reg__, __temp_reg__ の変更
avr-gcc のコード生成を見ていると __zero_reg__, __temp_reg__ は必要なようだ。他の AVR のコードに影響を与えないように変更しないといけないので、書きなおすようなことも困難。
さて、__zero_reg__, __temp_reg__ は、普通のAVR では r1/r0 だが、avrtiny10 では、r17/r16 に変わっている。実を言うと相当に変。save/restore するようなコードで __temp_reg__ が必要なことがあり、r16 を save しても壊してしまうのだ。
やはり save しないレジスタを割り当てなければならない。幸いなことに、レジスタ渡しの引数の数が 4 から 3 になっている。ここで空いた r18/r17 を使うべき。その他のレジスタはいつでも使える保証がない。
ただ、こうしてしまうと既存のコードの修正が必要になる。既存のコードとは何かというと libgcc.S 。(libgcc-fixed.S はハナから使えない) 。r16/r17 を選んだのも 多分影響を減らすという意味合いがあるのだろう。でもレジスタ数が減った以上影響が小さいわけがない。ここらへんはやむを得ないとして 正しい選択をすべきではないかとおもう。
基本的に、r18/r19 は 使ってしまってかまわない。r19 を 使った場合は、ret の前に
#if defined (__AVR_TINY__)
clr __zero_reg__ ; clear zero_reg (r19)
#endif
こういうコードを入れるだけで済む。ただ、r19 はもとは、第四引数だから その意味で使っているとすれば、根本的な対応が必要になる。r18 は temp だから 後始末もいらない。
で これらの定義をしているところを探して変更してみることにした。
avr-libc-1.7.1 :
common/asmdef.h
avr-gcc (4.4.3):
gcc/config/avr/avr.md
gcc/config/avr/libgcc.S
gcc/config/avr/libgcc-fixed.S
ところで、avr-libc を grep しているときに、avrtiny10 で disable されているものが多数あることが分かった。avr-libc はアセンブラのコードが結構あるのだが、とりあえず disable されている。また、可変引数の vfprintf などや strtok_P などといった LPM を使うものも disable されている。
あと、libgcc.S でも問題を見つけた。long x long の演算には 8 バイトの引数が必要だが、6 バイト分しかレジスタ渡しできない。コードは出来てしまうが動かない。
( 動かないのは、__mulhisi3, __umulhisi3, __mulsi3, __udivmodsi4 )
なかなか、avrtiny10 系は、大変なようだ。
現時点の感想:
レジスタを減らすというのは悪くはない考えだと思う。だが、gcc でのサポートについて検討が足りない。レジスタを減らせばメモリアクセスが増える。1word lds/sts 命令みたいな使えないものを入れないで 2word lds/sts を残し、変位付きのアクセスができる ldd/std も残すべきだったと思える。
- (
gcc のコード生成部をみなおして改めてそう思った。関数コールでセーブされるレジスタなど Y 以外にないも同然。基本的に変数は スタック上のメモリに置くことになる。やはり ldd/std がないと コード効率が落ちる。ldd/std 1命令で出来ることを 既存の命令の組み合わせにすると、Y に 加減算 してアクセスし元に戻すことになるから 5 命令かかる。
gcc での対応のやりかたも分かったから いずれ ldd/std を追加してみたい。 ( その場合 1word lds/sts は使えなくなるから 2word lds/sts も必要になる )
あと欲しいのは、movw や sbiw とかの 2 バイト系。ポインタを多用することになるから。だが、実装は重い。2clock かけて良いなら別だが ...
あと、レジスタを減らした以上影響は大きい。それでも単なる移植だから開発リソースさえあれば、なんとかなるはずだ。だが、現状を見ると開発リソースがあまりないように見える。いったいいつになったら対応が完了するのだろう? だいぶ時間がかかるのでは? という気がしてしょうがない。
まぁソースがあるから自力でも出来るはずだが、avr-libc のチェックと対応は気が遠くなりそう。
とりあえず整理
-
92-gcc-4.4.3-avrtiny10-fix2.patch(削除) -
91-avr-libc-1.7.1-chgtmp.patch(削除)
が add2 からの差分。一応整理した。 - これは lds/sts が出ない。だが 色々問題ありそうなレベル。
- __zero_reg__/__temp_reg__ を r18/r19 に変更して とりあえず libgcc.S と avr-libc はビルドできた。
- libgcc.S では __mulhisi3, __umulhisi3, __mulsi3, __udivmodsi4 の問題が残っている。
- avr-libc も disable されたものを チェックしていない。
queen_r.c はコンパイルできたが、vfprintf がないためビルドできない。
コードをチェックしてみたら、r19 を普通に使ってしまっているうえに、__zero_reg__ としても使っている。
まだまだだが、バイナリは一応置いた。 -
AVR_Toolchain-add3win.tar.gz-- コード生成変更 Windows 版 (6MB) -
AVR_Toolchain-add3lin.tar.gz-- コード生成変更 対応 Linux 版 (5MB)
あと、iort40.h (+ rtavr_defs.h ) を整理。
標準は XC3S50A で作れる最小構成分だけが使えるが、-DHAVE_LOCAL_DEFS を付けるとローカルの rtavr_defs.h をインクルードする。そして rtavr_defs.h は rtavr_defs.v から自動生成可能。
int main() {
int8_t i;
char *p = (PSTR("abc") + 0x4000);
for (i=0; i<100; i++) {
func_a(i & 1);
}
func = func_a;
while (*p) {
PORTB = *p;
p++;
}
return 0;
これでこういうコードをコンパイルしてみた。
0000006e <main>:
37: 93df push r29
38: 93cf push r28
39: 932f push r18
3a: b7cd in r28, 0x3d ; 61
3b: b7de in r29, 0x3e ; 62
3c: e090 ldi r25, 0x00 ; 0
3d: 2f89 mov r24, r25
3e: 7081 andi r24, 0x01 ; 1
3f: 5fcf subi r28, 0xFF ; 255
40: 4fdf sbci r29, 0xFF ; 255
41: 8398 st Y, r25
42: 50c1 subi r28, 0x01 ; 1
43: 40d0 sbci r29, 0x00 ; 0
44: dfea rcall .-44 ; 0x5e <func_a>
45: 5fcf subi r28, 0xFF ; 255
46: 4fdf sbci r29, 0xFF ; 255
47: 8198 ld r25, Y
48: 50c1 subi r28, 0x01 ; 1
49: 40d0 sbci r29, 0x00 ; 0
4a: 5f9f subi r25, 0xFF ; 255
4b: 3694 cpi r25, 0x64 ; 100
4c: f781 brne .-32 ; 0x7a <main+0xc>
4d: e2e2 ldi r30, 0x22 ; 34
4e: e4f0 ldi r31, 0x40 ; 64
4f: e28f ldi r24, 0x2F ; 47
50: e090 ldi r25, 0x00 ; 0
51: e6a0 ldi r26, 0x60 ; 96
52: e0b0 ldi r27, 0x00 ; 0
53: 938d st X+, r24
54: 939c st X, r25
55: c003 rjmp .+6 ; 0xb2 <main+0x44>
56: b986 out 0x06, r24 ; 6
57: 5fef subi r30, 0xFF ; 255
58: 4fff sbci r31, 0xFF ; 255
59: 8180 ld r24, Z
5a: 2388 and r24, r24
5b: f7d1 brne .-12 ; 0xac <main+0x3e>
5c: e080 ldi r24, 0x00 ; 0
5d: e090 ldi r25, 0x00 ; 0
5e: 912f pop r18
5f: 91cf pop r28
60: 91df pop r29
61: 9508 ret
できたのはこれ。大体合っていそうな感じ。 - subi/sbci が目立つ。ひとつは、フレームポインタ Y に 加算してアクセスし元に戻すような使い方。addi という命令は存在しない。
- __zoro_reg__ などはこのコードでは生成されないようだ。
- レジスタ渡しにならない パラメータのアクセス
int32_t func_a(int32_t a,int32_t b) {
return b+1;
}
こんな関数がどのように展開されるか見てみよう。
0000005a <func_a>:
2d: 93df push r29
2e: 93cf push r28
2f: b7cd in r28, 0x3d ; 61
30: b7de in r29, 0x3e ; 62
31: 5fcb subi r28, 0xFB ; 251
32: 5fdf subi r29, 0xFF ; 255
33: 9129 ld r18, Y+
34: 9139 ld r19, Y+
35: 9149 ld r20, Y+
36: 8158 ld r21, Y
37: 50c8 subi r28, 0x08 ; 8
38: 40d0 sbci r29, 0x00 ; 0
39: 5f2f subi r18, 0xFF ; 255
3a: 4f3f sbci r19, 0xFF ; 255
3b: 4f4f sbci r20, 0xFF ; 255
3c: 4f5f sbci r21, 0xFF ; 255
3d: 2f62 mov r22, r18
3e: 2f73 mov r23, r19
3f: 2f84 mov r24, r20
40: 2f95 mov r25, r21
41: 91cf pop r28
42: 91df pop r29
43: 9508 ret - r19 が使われてそのままになっている。これはダメだがおいておいて ...
gcc/config/avr/avr.h の CONDITIONAL_REGISTER_USAGE を直せば良さそう。 - Y を push して Y に SP を代入している。で Y+5,Y+6,Y+7,Y+8 を レジスタにロードして、Y を元にもどしている。
Y を使わず Z を使って、ロードするには、
0000005a <func_a>:
2f: b7cd in r30, 0x3d ; 61
30: b7de in r31, 0x3e ; 62
31: 5fcb subi r30, 0xFD ; 253
32: 5fdf subi r31, 0xFF ; 255
33: 9129 ld r18, Z+
34: 9139 ld r19, Z+
35: 9149 ld r20, Z+
36: 8158 ld r21, Z - __mulsi3 などは、__zero_reg__ 使っていないし、関数の頭でこれをして、最後に __zero_reg__ を clr して ret すれば良さそう。
いろいろ直す
-
92-gcc-4.4.3-avrtiny10-fix2.patch -
92-avr-libc-1.7.1-attiny10.patch
gcc の方だが、order_regs_for_local_alloc というのがある。CONDITIONAL_REGISTER_USAGE を直すのに合わせて変更した。
だが、libgcc2.c:400 (__mulvDI3) で
error: unable to find a register to spill in class 'GENERAL_REGS'
error: this is the insn:
(insn 986 985 987 135 ../../../../libgcc/../gcc/libgcc2.c:376 (set (reg:SI 46 [ D.2448 ])
(minus:SI (reg:SI 46 [ D.2448 ])
(reg:SI 409 [ temp.14 ]))) 892 {subsi3} (expr_list:REG_DEAD (reg:SI 409 [ temp.14 ])
(nil)))
というエラーが 出るようになってしまった。無理やりコンパイルしないようにすると通るから 問題が起きるのは これだけのようだ。
レジスタが足りないという問題だから解決できないかも。-- 困った。
それはともかく、avr-libc も 試しにいくつか直してみた。
common/asmdef.h common/macros.inc
にも手を入れて X_lpm を avrtiny10 に対応させ X_sbiw マクロを追加。これで いくつか修正。簡単なのはやり方が決まったから、少しづつ手を入れていこうと思う。
追記: lpm は全部不要ということを忘れていた。アドレスさえ分かれば普通に C でアクセスできる。
追記: 1word lds/sts に対応できたかも
1word の lds/sts では新たなリロケート情報が必要になる。他のリロケート情報を元にまねして付けていったら ... なにやらビルドできた。
ついでなので、今つくっている 機能縮小版の ldd/std にも対応した。まだ整理中だが、使えそうな雰囲気。
avr-objdump -mavr201 -d minicrypt.o |less
たとえば、こんな風に MACHINE まで指定すると lds が見える。
2a: 89 e8 ldi r24, 0x89 ; 137
2c: 9b ea ldi r25, 0xAB ; 171
2e: ad ec ldi r26, 0xCD ; 205
30: bf ee ldi r27, 0xEF ; 239
32: 80 a9 sts 0x0000, r24
34: 90 a9 sts 0x0000, r25
36: a0 a9 sts 0x0000, r26
38: b0 a9 sts 0x0000, r27
MACHINE を指定しないと ldd/std と区別がつかないので次のようになる。
2a: 89 e8 ldi r24, 0x89 ; 137
2c: 9b ea ldi r25, 0xAB ; 171
2e: ad ec ldi r26, 0xCD ; 205
30: bf ee ldi r27, 0xEF ; 239
32: 80 a9 ldd r24, Z+48 ; 0x30
34: 90 a9 ldd r25, Z+48 ; 0x30
36: a0 a9 ldd r26, Z+48 ; 0x30
38: b0 a9 ldd r27, Z+48 ; 0x30
そして、このオブジェクトは gcc で生成したもの。オプションを付けることで いくつかの 出力方法に対応できた。
-mldssts1
-mldssts2
-mno-ldssts
-mreduced-lddstd
-mldssts1 と -mldssts2 は なにが違うかというと バイト数の計算。ブランチ命令で届く範囲の計算が変わる。アセンブラにまで 設定が行かないので アセンブラは 1word lds/sts しか出さない。( 対応済みでないアセンブラは 2word lds/sts ) 。
対応済みでない アセンブラでは 2word lds/sts になってしまい困るので -mno-ldssts というのも付けてみた。ただこれはバグっている。binutils がうまく行くようなら外す。
ついでにつけた -mreduced-lddstd は、ldd/std 命令を使うようにする。ただし 1word lds/sts と命令が重なるので disp は 0-31 の範囲だけ。いまのところ作ってみただけだが、rtavr で対応する目処が立ったので がんばるかも。
パッチ集は整理した。
これに対応するバイナリも作る。
バイナリは一応作ったのだが、問題がある。で、修正しようとしているのだが、ひとつの問題を直せていない。
という問題で、avr-objdump.exe -mavr201 -r testmain.o とすると
testmain.o: file format elf32-avr
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000026 R_AVR_LO8_LDI_GS var_b
00000038 R_AVR_LO8_LDI_GS var_b
0000003c R_AVR_LO8_LDI .progmem.data
0000003e R_AVR_HI8_LDI .progmem.data
00000050 R_AVR_LO8_LDI_GS var_b
00000052 R_AVR_LO8_LDI_GS var_a+0x00000001
00000054 R_AVR_LO8_LDI_GS var_a
00000056 R_AVR_LO8_LDI_GS var_b
こうなる。var_a/var_b のところを R_AVR_7_LD1 にしているつもりが、違うタグになる。
タグだけが違うようなかんじで、 リンクの際に
testmain.c:(.text+0x54): warning: internal error: out of range error
というエラーになっている。... 困った。
これは、bfd/reloc.c に並べる順番が関係していた。R_AVR_7_LD1 を途中にいれていたのだが、最後にしたら OK 。
パッチ集はこれで FIX 。バイナリは準備中。
あと binutils で、PSTR() で取るアドレスに対して 0x4000 を自動的に加算しようかと思う。これから調査。
PSTR の処理の調査
まず、PSTR 自体は avr-libc の avr/pgmspace.h で定義されている。
展開すると
#define PSTR(s) ((const __attribute__((__progmem__)) char *)(s))
こうなっている。
で、gcc でこの attribute の処理をするわけだが、
どうも text_segment_operand () というのが関係あるらしい。コメントに
/* Return true if OP is a program memory reference.*/
なんて書いてある。
だが、結局 ただのシンボルのアクセスになるようだ。
.type __c.1676, @object
.size __c.1676, 4
__c.1676:
.string "abc"
:
:
ldi r24,lo8(__c.1676)
ldi r25,hi8(__c.1676)
こんな感じのアセンブラ出力。
.o のリロケーション情報を見てみると
0000003c R_AVR_LO8_LDI .progmem.data
0000003e R_AVR_HI8_LDI .progmem.data
こんな風になっている。
これを処理するリンカのコード
case R_AVR_HI8_LDI:
contents += rel->r_offset;
srel = (bfd_signed_vma) relocation + rel->r_addend;
srel = (srel >> 8) & 0xff;
x = bfd_get_16 (input_bfd, contents);
x = (x & 0xf0f0) | (srel & 0xf) | ((srel << 4) & 0xf00);
bfd_put_16 (input_bfd, x, contents);
break;
ヘッダファイル、gcc, アセンブラ、リンカ。どこにでも対応を入れられそうだが、どこが良いのだろう?
リンカでは、セクション自体の定義を変える対応すら可能そうだ。
まずは既存の AVR でオフセットを加算しているようなものがないか調べる。既にあればマネをすれば良い。
問題は、存在しない場合。自分で入れるなら 変更が最も少ないところに入れたい。今のところ アセンブラかなと思っている。gcc のほうが良いかも知れない。
ヘッダファイルでは、似たものを全部直さないといけないし、もれがあるとだいなしだから避けたい。セクション自体の定義も 他に影響ありそうなのでパス。
アセンブラだと 対応するのは LDI 命令だけ。.progmem.data ということも知っているから 加算することは可能だろう。
gcc になると 難しそうな感じだが、pm_hi8() とか gs() とか pm() とかがあるから なにか対応策がありそう。
これをやりたい理由を書き忘れた。avr-libc での対応をどうするか決めたいのが理由。結構 lpm を使っているし xxx_P という関数群もある。修正方針が決まらないと手をつけられない。-- だが、へたに決めることもできない。なんとも悩ましい。
バグ修正と ldd/std 対応。
AVR互換コアのテストをしていて、バグを見つけた。st -Z x2 のところで、アドレスを -2 しておくのが -1 になっていた。
これを直したら、動かなかったのが直った。で、気をよくして ldd/std 対応もちゃんと入れることにした。
avrtiny10 対応は、オリジナル or 専用コード みたいなつくりになっているので、オリジナルを使う条件を 変更してやることで ldd/std を使えるようにする。それも全部は、入れない。単位処理が 2 倍以上のコードになっているところのみにする。
line-no ldd/std reg # offset GET_MODE lim
2306 LDD REG_Y 1 XEXP(x,1) src -1
2317 LDD REG_Z 1
2473 LDD REG_Y 1 disp src -1
2488 LDD REG_Z 1
2508 LDD REG_Y 2 disp src -1
2521 LDD REG_Z 2
3255 STD REG_Y 4 disp dest -3
3273 STD REG_Z 4
3569 STD REG_Y 1 XEXP(x,1) dest -1
3580 STD REG_Z 1
3706 STD REG_Y 1+ST XEXP(dest,1) dest -1
3717 STD REG_Z 1+ST
3854 STD REG_Y 2 disp dest -1
3867 STD REG_Z 2
それでもこれだけある。問題は lim で示した 境界条件。-- 実は計算方法が良くわからない。どうも 63 という数がよく出てきているので、オフセット(らしき数字) の条件を 31 ではなく 30 にしている。あと、4 バイト系は さらに -2 。
これら ldd/std は、-mreduced-lddstd オプションを付けないと出ない。ルールを作って変更しているから、他には影響を与えない。
パッチとバイナリは出来次第 upload する。コード生成が変わっただけなので、バイナリは cc1/cc1plus と avrtiny10 のライブラリのみ。