演算ライブラリについて
sdccには基本的な標準ライブラリが入っています。 その中には
stdio.h stddef.h stdlib.h string.h
など、C言語として当たり前のものもあるのですが、その他に、基本的な演算や浮動小数点なども含まれます。
たとえば、整数の掛け算や割り算をする場合も、関連するライブラリがリンク時に埋め込まれます。z80は標準で掛け算や割り算の命令を持たないので、これは仕方のないところです。
ただ、演算ライブラリは関数単位ではなくオブジェクト単位で埋め込まれるので、不要なものまで入ってしまうことがあります。
演算ライブラリのリンク例
たとえば、次のような簡単なプログラムをコンパイルしてみましょう。
-- test.c long mul(long a, long b) { return a * b; } void main () { mul(654321, 123456); }
今回のプログラムは試しやすいようにアーカイブを用意しました。
Makefileが入っていますのでmakeのみでコンパイル可能です。 まずは、アーカイブのsample1ディレクトリでmakeしてみてください。
Makefileにはいろいろ書いてありますが、要は次のようにしているのと同じです。
% sdcc -mz80 --code-loc 0 --no-std-crt0 test.c
今回は話を簡単にするため、crt0は使っていません。
さて、これでオブジェクトのてtest.ihxのほか、マッピングファイルのtest.mapが出力されていると思います。
mapファイルは、グローバルシンボルがどこに配置されているか、各エリアのサイズがどのくらいかが示されています。
そのmapファイルを見てみましょう。
-- test.map(抜粋) (中略) Area Addr Size Decimal Bytes (Attributes) -------------------------------- ---- ---- ------- ----- ------------ _CODE 0000 090D = 2317. bytes (REL,CON) Value Global -------- -------------------------------- 0000 _mul 0000 _mul_start 0032 _main 0032 _main_start 0032 _mul_end 004A __mullong_rrf_s 004A __mullong_rrx_s 004A _main_end 004D __modslong_rrf_s 004D __modslong_rrx_s 0050 __modulong_rrf_s 0050 __modulong_rrx_s 0053 __divslong_rrf_s 0053 __divslong_rrx_s 0056 __divulong_rrf_s 0056 __divulong_rrx_s 0059 __mulint_rrf_s 005F __divsint_rrf_s 0065 __divuint_rrf_s 006B __mulschar_rrf_s 0071 __divschar_rrf_s 0077 __muluchar_rrf_s 007D __divuchar_rrf_s 0083 __modschar_rrf_s 0089 __moduchar_rrf_s 008F __modsint_rrf_s 0095 __moduint_rrf_s 009B __rrulong_rrf_s 00A1 __rrslong_rrf_s 00A7 __rlulong_rrf_s 00AD __rlslong_rrf_s 00B3 __divschar_rrx_s 00BA __divschar_rrx_hds 00C1 __modschar_rrx_s 00C8 __modschar_rrx_hds 00CF __divsint_rrx_s 00DB __divsint_rrx_hds 00E3 __modsint_rrx_s 00EF __modsint_rrx_hds 00F7 __divuchar_rrx_s 00FE __divuchar_rrx_hds 0105 __moduchar_rrx_s 010C __moduchar_rrx_hds 0113 __divuint_rrx_s 011F __divuint_rrx_hds 0127 __moduint_rrx_s 0133 __moduint_rrx_hds 013B .div8 013B .mod8 0143 .div16 0143 .mod16 0180 .divu8 0180 .modu8 0183 .divu16 0183 .modu16 01B8 __rrulong_rrx_s 01D5 __rrslong_rrx_s 01F2 __rlslong_rrx_s 01F2 __rlulong_rrx_s 020F __modslong 020F __modslong_start 02E8 __modslong_end 02E8 __modulong 02E8 __modulong_start 03FF __divslong 03FF __divslong_start 03FF __modulong_end 04E1 __divslong_end 04E1 __muluchar_rrx_s 04F4 __mulschar_rrx_s 04FB __mulschar_rrx_hds 0507 __mulint_rrx_s 0513 __mulint_rrx_hds 0513 __muluchar_rrx_hds 052C __divulong 052C __divulong_start 0610 __divulong_end 0610 __mullong 0610 __mullong_start 090D __mullong_end
いかがでしょうか。test.c本体は0x0000~0x0049に配置されています。staticスコープではない関数はstartとendのシンボルが付加されます。_main_endなどの終了位置は終了アドレス+1を示します。
そして、0x004a~0x090cはすべて演算用モジュールです。見ると、divだのmodだの、関係ないものまでたくさん入っていて、そのサイズは実に2,243バイトに達します。
小さなコードを生成するためには
では、もし標準ライブラリをまったくリンクしないとどうなるでしょう。
% sdcc -mz80 --code-loc 0 --no-std-crt0 --nostdlib test.c ?ASlink-Warning-Undefined Global '__mullong_rrx_s' referenced by module 'test'
こうなると思います。つまり、掛け算で'__mullong_rrx_s'という関数を呼び出しているわけです。
こ の関数が定義されているのは、 SDCC/lib/z80/stubs.o というオブジェクトなのですが、実はこのオブジェクトで演算の本体が実装されているわけではありません。名前のとおりこのオブジェクトはただのスタブで、 演算に関するありとあらゆるオブジェクトを参照するようになっています。
リンカはオブジェクト単位ですべての参照を解決しないと出力を生成しないため、スタブが参照しているオブジェクトをすべて埋め込む結果となってしまいます。
オブジェクトを追いかけてみる
ライブラリがリンクするオブジェクトを追いかけるためには、オブジェクトファイルの構造を知ることが必要ですが、ここでは最低限知っておけばいいことだけ挙げます。
先ほどのstubs.oを見ると、次のようになっています。
-- stubs.o (抜粋) (中略) S __muluchar_rrx_s Ref0000 S __mullong Ref0000 (中略) S __rrulong_rrf_s Def0051 S __mullong_rrx_s Def0000 (中略)
最初の方にこのような"S"で始まる行がたくさんあります。これはシンボル定義で、シンボル名と参照・定義の種別を表していま す。Refとあるのは他のオブジェクトで定義されているシンボルの参照、Defとあるのがこのオブジェクトで定義されているシンボルと定義されているアド レスです。
リンカはリンクしようとしたオブジェクトにRefで示されるシンボルがあると、それに対応するDefのシンボルが定義されてい るオブジェクトをリンクします。stubs.oは大量のRefがあるため、それに対応するDefのあるオブジェクトをすべてリンクするまではリンクが終了 しません。
ソースを追いかけてみる
この問題を解決するためには、オブジェクトファイルを直接いじる手もあるのですが、幸いsdccにはソースがついているので、ソースを直接インポートしてしまうのが手っ取り早いでしょう。
ソースは、 SDCC/lib/src/z80/ に入っています。stubs.s を見ると、以下のようになっています。
-- stubs.s (抜粋) __mullong_rrx_s:: __mullong_rrf_s:: jp __mullong
何のことはない、__mullongに飛んでいるだけです。__mullongは
SDCC/lib/src/__mullong.c
に入っています。
そこで、stubs.sをコピーして、必要なmullong_rrx_s以外の定義を削除してしまいます。次に__mullong.cを個別にコンパイルしてリンクします。すると次は、
?ASlink-Warning-Undefined Global '__muluchar_rrx_s' referenced by module '_mullong'
というリンクエラーが出ます。この定義は
SDCC/lib/src/z80/mul.s
に入っているもっとも基本のライブラリなので、これもアセンブルするようにします。
これらを済ませたものがアーカイブのsample2ディレクトリに入っています。
このディレクトリでmakeしてみてください。リンクエラーは出なくなったと思います。
結果生成されたmapファイルを見てみると、
-- test.map(抜粋) Area Addr Size Decimal Bytes (Attributes) -------------------------------- ---- ---- ------- ----- ------------ _CODE 0000 0395 = 917. bytes (REL,CON) Value Global -------- -------------------------------- 0000 _mul 0000 _mul_start 0032 _main 0032 _main_start 0032 _mul_end 004A __mullong 004A __mullong_start 004A _main_end 0347 __mullong_end 0347 __mullong_rrf_s 0347 __mullong_rrx_s 034A __muluchar_rrx_s 035D __mulschar_rrx_s 0364 __mulschar_rrx_hds 0370 __mulint_rrx_s 037C __mulint_rrx_hds 037C __muluchar_rrx_hds
今度はリンクされる関数がかなり少なくなりました。リンクされたライブラリの大きさは818バイトです。
__mullong.cを最適化したり、mul.sで使われていないルーチンを削ったりすることでもっとサイズを削減することも可能です。
まぁ、そのような場合は計算用ライブラリを個別に用意して、それを呼び出した方がいいかもしれません。
ちなみに、SDOSでは、以下については標準ライブラリは使わず、アセンブリ言語で独自の実装を行っています。
- 乗算(16ビット x 16ビット = 32ビット)
- 除算(32ビット / 8ビット = 32ビット)
どちらが絶対的によいと言うわけではないのですが、16ビットまでの大部分の演算はライブラリを呼び出さずにインライン展開されるようなので、必要な演算の種類やメモリ・速度の制約などのトレードオフを勘案して使い分ければいいと思います。
匿名
画面が真っ暗、でもカーソルは出てる状況。
探して、ここにたどり着きました。
パスワード入力で、復活!
修理に出す寸前でした。ホントにありがとう!