開発日記

数日前にシミュレータ上でのテストは通った. 性能とメモリ使用量は課題はあるものの MCU 用にコンパイルして調整することになった.

久しぶりとなる MCU 用のコンパイルでは意外なことに 0x1000 bytes だけの RAM は余裕があり、0x6000 bytes の ROM の使用率が 90% になってしまっていた.
ROM の使用量はひとまずとして MCU 特有の DMA がちゃんと動くかを1日調整. 同じ descriptor と memory cycle を使う場合はやらなくてもいい初期化が結構あったので簡略化. ここで結構バグが見つかり、初期化を省いた分、起動が早くなった.

大体は動いているようなので ROM の使用量の削減に入る. text mode と呼んでいる serial terminal からコマンドを打つモードであまり使っていない機能を ifdef で分離したり削除した. その後、elf file を逆アセンブルして分析. C 標準ライブラリの主に string.h の関数がかなり食っているのでそこを調整.

string.h の関数とは文字列処理は別にして、memcpy(), memset(), memcmp() の3つがものすごく重要. これらは #include <string.h> がなくても暗黙的に呼ばれることもある. また引数の src や length が定数の場合は関数を呼ばずにインライン展開されることもある.

実際に逆アセンブルしてみるとインライン展開はほとんどなく関数の呼び出しが多い. 今回は下記のソースで memset() を暗黙的に呼び出していた.

(0 で埋めた配列の宣言)
	int content_length[CT_NUM] = {0, 0, 0, 0};
	int content_num[CT_NUM] = {0, 0, 0, 0};
(そこそこのサイズがある struct の宣言)
	if(1){
		const struct tcx_parameters t = {
			.evctrl = 
				TC_EVCTRL_TCEI | TC_EVCTRL_EVACT_RETRIGGER |
				TC_EVCTRL_MCEO1,
			
			.per = 40,
			.cc = {10, wait_for_next, 0, 0},
			.ctrlc = TC_CTRLC_INVEN0,
			.ctrlb = TC_CTRLBSET_CMD_RETRIGGER | TC_CTRLBSET_ONESHOT,
			.ctrla = TC_CTRLA_RUNSTDBY |
				TC_CTRLA_PRESCALER_DIV1 |
				TC_CTRLA_WAVEGEN_NPWM
		};
		tc_8bitmode_init(3, &t);
		tc_8bitmode_init(4, &t);
	}

配列の宣言は初期化をせず、宣言後に memset() を明示して呼び出せば暗黙は消せる. struct のほうは... 0 で埋めている変数が少ないのになぜ memset() を呼び出すのかARMのアセンブラは読めないことも手伝い理解できない. static const にすれば .rodata に入るのだがどっちが使用率が低いのか判断にも困る.

MCU用のソースコードブートローダとアプリケーションで2層になっている. ブートローダ側に mem なんとかはリンクされているので、アプリケーションからブートローダ側のリンクされた関数を利用することで ROM 使用量を減らす.
当初の方針として3つの関数は prefix に my_ をつけて my_memset() として別の関数にするつもりだったが、この2つの暗黙的な呼び出しにうまく対応できない.

そこで my_memset() の名前を memset() にして libc.a の memset() をリンクしないようにしたら、これは想定通りに動いた. weak 属性が付いてるのかもしれないが調べていない.

その他、標準ライブラリをリンクしている部分を見つけ出して削っていったわけだが、標準の名の通り引数の制限がかなり緩くその条件でも規格通りに動くようになっているので命令のコードがかなり長いのが気になった. strtoul() *1の中で <ctype.h> の isspace() を呼んでいるが ' ' を認識すればいいだけで容量を食うので削った. あとは newlib にほとんどインライン展開が効いてないので想定とは違っていた.

それらの削減作業により標準ライブラリは string.h の主要3関数は別のビルドに動的にリンクして、strlen() のみ残した. strlen() も長いコードになるのが気になるが、使用率が 75% にまで減ったのでそこまでにした.

他に言えることは機能を削ったりするのはわりと頭を使わなくて済むので途中でいやにならなくて助かる.

*1:newlib からソースを取ってきて別名関数として手を入れたもの