/*	
 *	clippce.c
 *
 *	P/ECE専用ルーチンを集めました。
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 Naoyuki Sawa
 *
 *	* Wed Apr 24 09:26:00 JST 2002 Naoyuki Sawa
 *	- 作成開始。
 *	* Sun Jun  2 04:45:00 JST 2002 Naoyuki Sawa
 *	- pceAppInit()で高速版pceLCDDrawObjectをインストールし、
 *	  pceAppExit()で高速版pceLCDDrawObjectをアンインストールするようにしました。
 *	  もう、アプリケーション側でこれらの作業を行う必要はありません。
 *	* Sun Jun  2 20:07:00 JST 2002 Naoyuki Sawa
 *	- pceLCDDrawObject()をclipdobj.cに分離。
 *	* Thu May 13 22:00:00 JST 2002 Naoyuki Sawa
 *	- 空きメモリの大部分をmalloc()用ヒープとして確保するように変更。
 *	- 失敗する可能性のある初期化処理を、pceAppInit()からapp_init()に分離。
 *	- 下位レベル関数/ダミー関数の作成開始。
 *	* Fri Jun 21 19:22:00 JST 2002 Naoyuki Sawa
 *	- GSMのユーティリティ関数を追加。
 *	* Fri Jun 22 09:09:00 JST 2002 Naoyuki Sawa
 *	- 割り込みルーチンgsm_isr()が、psrを壊してしまっていたのを修正。
 *	  GSM再生ルーチンについて既知のバグはとりあえず全部つぶしたと思う。
 *	* Wed Jun 26 23:28:00 JST 2002 Naoyuki Sawa
 *	- usleep()追加。
 *	- lcd_init()追加。pceAppInit()で必ず呼ぶようにしました。
 *	* Sta Jun 29 21:11:00 JST 2002 Naoyuki Sawa
 *	- GSM再生関数のプレフィスクをstream_*からgsm_*に変更。
 *	  GSMライブラリ内の関数名とぶつかる恐れがあるのでstream_*にしていましたが、
 *	  どうやら大丈夫そうなので、わかりやすいgsm_*に変更しました。
 *	* Tue Jul  2 19:00:00 JST 2002 Naoyuki Sawa
 *	- fpak_find()のバグ修正。
 *	  ファイル情報の読み込み位置(ofs)を進めるのを忘れていたため、
 *	  最初のファイル以外は見つけられなかったのを修正しました。
 *	- malloc()用ヒープを0x77で埋めておくようにしました。（デバッグし易いように）
 *	* Thu Jul  4 05:30:00 JST 2002 Naoyuki Sawa
 *	- file_inflate_size(),fpak_inflate_size()を追加。
 *	  生のzlib圧縮形式には、圧縮前のファイルのサイズが含まれていないからです。
 *	* Sat Oct 12 06:07:00 JST 2002 Naoyuki Sawa
 *	- zlib関連ルーチンをclipzlib.cに分離しました。
 *	- GSM関連ルーチンをclipgsm.cに分離しました。
 *	* Sat Oct 12 10:35:00 JST 2002 Naoyuki Sawa
 *	- 64ビット整数演算ライブラリ関数を追加しました。テストはまだ不充分です。
 *	* Mon Oct 14 12:31:00 JST 2002 Naoyuki Sawa
 *	- render_object()を追加しました。
 *	* Thu Oct 17 18:00:00 JST 2002 Naoyuki Sawa
 *	- pceAppInit()/pceAppExit()に、サウンドの初期化・停止を追加しました。
 *	  音楽ライブラリの初期化・停止は行っていませんので、
 *	  必要に応じてアプリケーション内で初期化・停止を行ってください。
 *	  （ここで音楽ライブラリの初期化・停止を行うと、音楽ライブラリが必要ない
 *	    アプリケーションまで、一律50KB程度srfファイルサイズが増加してしまいます）
 *	- sound_play()/sound_stop()を追加しました。
 *	* Sun Nov 3 00:12:00 JST 2002 Naoyuki Sawa
 *	- pceAppExit()に、赤外線通信の停止を追加しました。
 *	* Tue Nov 5 19:00:00 JST 2002 Naoyuki Sawa
 *	- render_font()/render_string()/render_printf()を追加しました。
 *	* Wed Nov 6 06:15:00 JST 2002 Naoyuki Sawa
 *	- render_font_framed()/render_string_framed()/render_printf_framed()を追加しました。
 *	  9回重ね書きしているので、とても遅いです。
 *	  ビットパターンを重ね合わせるようにすれば高速化できますが、まだ高速化していません。
 *	* Sun Nov 10 19:01:00 JST 2002 Naoyuki Sawa
 *	- ユーザーコンテキスト用のスタックアドレスを、これまでは固定で0x1a00に設定していましたが、
 *	  今回より、アプリケーション開始時の%sp相対で設定するように変更しました。(%sp-1.5KBに設定)
 *	  このように変更した理由は、スタックをSRAM上に取った場合にも対応できるようにするためです。
 *	* Sat Nov 16 09:34:00 JST 2002 Naoyuki Sawa
 *	- ユーティリティ：ストリーム再生を追加しました。各種ストリーム再生の共通部分です。
 *	  GSM関連ルーチンもこれを使うように変更しましょう。(TODO:※)
 *	- ユーティリティ：ファイルにfile_address()を追加しました。
 *	* Tue Nov 19 12:30:00 JST 2002 Naoyuki Sawa
 *	- app_init()に、高速RAMへのコード転送を追加しました。
 *	  pceAppInit()でなくapp_init()で行う理由は、転送サイズ検査でDIE()する可能性があるからです。
 *	  もしもpceAppInit()内でDIE()を使うと、DIE()は無効となり、ハングアップしてしまいます。
 *	  pceAppInit()の中ではまだ高速RAMへの転送を行っていないので、pceAppInit()の中に
 *	  高速RAMに転送されるコードを利用するような初期化を置かないよう、注意してください。
 *	- new_LCDDrawObject()、render_font()、mod_seq()/mod_mix()を高速RAMに配置しました。
 *	* Mon Nov 26 00:47:00 JST 2002 Naoyuki Sawa
 *	- 標準入出力関数を実装したので、file_address()以外のファイルユーティリティをOBSOLATEにしました。
 *	- zlib/unzip.cで、圧縮ファイルの解凍と書庫ファイルの管理ができるようになったので、
 *	  my_compress()以外のZlibユーティリティと、ファイルパックユーティリティをOBSOLATEにしました。
 *	* Mon Nov 28 19:00:00 JST 2002 Naoyuki Sawa
 *	- file_select()を追加しました。
 *	- strdup()を追加しました。
 *	* Mon Nov 30 08:06:00 JST 2002 Naoyuki Sawa
 *	- pceAppInit()にstderr_open()を追加しました。
 *	- pceAppExit()にstderr_close()を追加しました。
 *	* Sat Dec 7 06::00:00 JST 2002 Naoyuki Sawa
 *	- turbo()関数を追加しました。
 *	- pceAppExit()内にturbo(0)呼び出しを追加しました。
 *	* Sun Dec 8 06:08:00 JST 2002 Naoyuki Sawa
 *	- mod_init()をテンポエフェクトに対応させました。
 *	* Tue Dec 10 23:38:00 JST 2002 Naoyuki Sawa
 *	- ismbblead()/ismbbtrail()を追加しました。
 *	* Wed Dec 11 20:50:00 JST 2002 Naoyuki Sawa
 *	- strcasecmp()/strncasecmp()を追加しました。
 *	* Wed Dec 12 01:03:00 JST 2002 Naoyuki Sawa
 *	- ismbbtrail()のコード範囲を修正しました。
 *	- strtok()の置き換え用実装を追加しました。
 *	* Mon Jan 27 04:46:00 JST 2003 Naoyuki Sawa
 *	- スタックあふれ検査を追加しました。（STACKCHECK、stack_addr、stack_check_install、stack_check）
 *	- pceAppInit()にスタックあふれ検査の準備を追加しました。（stack_check_install）
 *	- pceAppProc()にスタックあふれ検査を追加しました。（stack_check）
 *	* Sat Feb 1 14:08:00 JST 2003 Naoyuki Sawa
 *	- MODドライバを可変チャネル数に対応しました。
 *	* Sat Feb 22 17:35:00 JST 2003 Naoyuki Sawa
 *	- pceAppExit()にstream_stop()の呼び出しを追加しました。
 *	  本当はアプリケーションの終了処理内でmod_stop()等をきちんと呼ぶのが望ましいのですが、
 *	  現在のところmod_stop()以外のストリーム再生はほとんど使っておらず、
 *	  mod_stop()ならばstream_stop()と等価（stream_stop()以外の処理は行っていない）ので、
 *	  簡単のためにライブラリ内でstream_stop()を呼んでしまうことにしました。
 *	  ストリーム再生を使っていなくても、stream_stop()の呼び出しは安全です。
 *	- my_unzip()を追加しました。
 *	* Mon Feb 24 12:30:00 JST 2003 Naoyuki Sawa
 *	- BIOS1.18のpceTimerAdjustPrecisionCount()不具合に対応、修正版pceTimerAdjustPrecisionCount()を追加しました。
 *	* Mon Feb 24 19:00:00 JST 2003 Naoyuki Sawa
 *	- mod_tick()にて、エフェクトF00(SetSpeed=0)による演奏停止をサポート。
 *	* Wed Feb 26 12:45:00 JST 2003 Naoyuki Sawa
 *	- my_unzip()をclippce.cからclipzlib.cへ移動しました。
 *	  （zlib/zlpを使わないアプリケーションにリンクされてしまうのを防ぐため）
 *	* Wed Feb 26 21:58:00 JST 2003 Naoyuki Sawa
 *	- file_address()にファイルサイズを受け取るためのポインタ引数を追加しました。
 *	* Sat Mar 22 05:51:00 JST 2003 Naoyuki Sawa
 *	- 64ビット除算の補助ルーチン__div00/01/10/11()を、グローバルからstaticに変更。
 *	  警告抑制のため、__attribute__((unused))を利用しました。
 *	- 64ビット乗算を、乗算器を利用した高速版に変更。
 *	  これまで使っていた古典的な低速版も、参考のために残してあります。
 *	* Sat Mar 22 19:00:00 JST 2003 Naoyuki Sawa
 *	- __divdi3()での正負判断を、「scan1」を使う方法から「cmp 0」を使う方法に変更。
 *	* Sun Mar 23 05:53:00 JST 2003 Naoyuki Sawa
 *	- pow()の置き換え用実装を追加しました。
 *	* Tue Apr 1 22:00:00 JST 2003 Naoyuki Sawa
 *	- stack_addr()を、高速RAMにスタックを取った場合にも正しく動くよう修正。
 *	* Thu May 1 12:30:00 JST 2003 Naoyuki Sawa
 *	- カーネルの不具合対処コードのうち、PieceSystem 1.20で修正されたものを削除しました。
 *	  修正版pceTimerAdjustPrecisionCount() → 削除
 *	  lcd_init() → 削除
 *	* Sun Sep 14 18:44:00 JST 2003 Naoyuki Sawa
 *	- file_select()をOBSOLATEにしました。
 *	- MOD演奏ルーチンをclipmod.cへ移動しました。
 *	* Sun Sep 14 21:44:00 JST 2003 Naoyuki Sawa
 *	- システムスタックサイズを変更できるようにしました。
 *	  コンパイルオプションで +defsym _sysstack=0x???? と指定してください。
 *	* Sat Nov 22 06:00:00 JST 2003 Naoyuki Sawa
 *	- 初期状態では標準エラー出力なしに変更しました。
 *	  標準エラー出力を利用するアプリケーションは、最初にstderr_open()を呼んでください。
 *	  stderr_open()を呼ばなかった場合、全ての fprintf(stderr, ...) 出力は無視されます。
 *	* Sat Nov 22 06:00:00 JST 2003 Naoyuki Sawa
 *	- turbo()関数にメモリウェイト1WAIT設定機能を追加しました。
 *	* Tue Jan 27 12:52:00 JST 2004 Naoyuki Sawa
 *	- 下位レベル関数read()を追加しました。EPSONライブラリのsscanf()関数を使うためです。
 *	* Mon Mar 08 12:30:00 JST 2004 Naoyuki Sawa
 *	- stream_play()関数に、FRAM4領域スタック切り替え機能を追加しました。
 *	  これまでプレイヤー個別で行っていた処理を、ストリーミング処理側にまとめるためです。
 *	* Mon Mar 15 12:30:00 JST 2004 Naoyuki Sawa
 *	- mmap()関数を追加しました。
 *	* Mon Jun 7 10:23:00 JST 2004 Naoyuki Sawa
 *	- 高速版pceLCDDrawObject()の2bit描画対応版を削除し、4bit描画対応版に置き換えました。
 *	* Tue Jun 8 06:00:00 JST 2004 Naoyuki Sawa
 *	- new_LCDDrawObject()の4bit描画ルーチン呼び出しにて、
 *	  マスク有無の判断をnew_LCDDrawObject()から呼び出し先のPDW_Draw_4BN()へ移しました。
 *	* Tue Jun 8 19:00:00 JST 2004 Naoyuki Sawa
 *	- カスタムpceLCDDrawObject()は4bit描画のためだけに使うことになったので、
 *	  高速切替・16階調表示使用時のみインストールするようにしました。
 *	  カスタムpceLCDDrawObject()のコードも、clipldir.cおよびframldir.cへ移しました。
 *	  高速切替・16階調表示を使わない場合はカスタムpceLCDDrawObject()をリンクしないので、プログラムサイズが節約できます。
 *	* Thu Jun 17 05:14:00 JST 2004 Naoyuki Sawa
 *	- メモリ管理を、EPSONライブラリmalloc()から、P/ECE API pceHeapAlloc()に変更しました。
 *	  PieceSystem 1.18 以前のpceHeapAlloc()はヒープ破壊Bugがあったため、これまではEPSONライブラリmalloc()を使っていましたが、
 *	  PieceSystem 1.20 にてこのBugが修正され、問題なく使えるようになりました。
 *	  EPSONライブラリmalloc()よりもpceHeapAlloc()の方が、プログラムBug等によるヒープ破損検出機能が優れているので、
 *	  メモリ管理をすべて、これまで使っていたEPSONライブラリmalloc()から、P/ECE API pceHeapAlloc()に切り替えることにしました。
 *	  本モジュールにてmalloc()/calloc()/realloc()/free()を再定義し、EPSONライブラリ内の同名関数をオーバーライドします。
 *	* Thu Jun 17 07:27:00 JST 2004 Naoyuki Sawa
 *	- PieceSystem 1.20 現在のpceHeapRealloc()バグを回避するため、realloc()からのpceHeapRealloc()利用を取りやめました。
 *	* Thu Sep 16 06:00:00 JST 2004 Naoyuki Sawa
 *	- system_context,user_contextを、グローバル変数→スタティック変数に変更しました。
 *	* Thu Sep 16 12:30:00 JST 2004 Naoyuki Sawa
 *	- システム/ユーザー各コンテキストの%r0～%r3を、CONTEXT構造体に保存するのでなく、スタック上に保存するよう変更しました。
 *	  システム/ユーザー各コンテキストについて、保存するのは%spだけになったので、CONTEXT構造体を廃止しました。
 *	- 今回変更したような、レジスタをスタック上に保存する手法の方が、多くのマルチタスクシステムの一般的な手法です。
 *	* Fri Nov 05 19:00:00 JST 2004 Naoyuki Sawa
 *	- pceAppNotify()をライブラリに含めました。
 *	  アプリケーションがNotify処理を必要とする場合は、atnotify()を呼び出して関数を登録してください。
 *	  pceAppNotify()をライブラリに含めた理由は次のとおりです。
 *	  これまで、アプリケーションでpceAppNotify()を定義する場合、C:/usr/PIECE/lib/src/defnotify.cのコードをコピーする必要がありました。
 *	  pceAppNotify()の仕様では、通知コードによって行うべき規定の処理が違っているからです。
 *	  APPNF_SMSTARTに対してはAPPNR_ACCEPTを返す、APPNF_SMREQVBUFに対してはpceLCDSetBuffer(_def_vbuff)を行ってAPPNR_ACCEPTを返す、
 *	  それ以外の通知コードに対してはAPPNR_IGNOREを返す、というのが規定の処理です。
 *	  pceAppNotify()をアプリケーション内で定義する場合、これらの処理を毎回書かなければいけないのは不便だと考えました。
 *	  そこで、規定の処理はライブラリ内で行うことにし、アプリケーションは必要な処理だけを行えばいいようにしました。
 *	  今回の変更より、アプリケーションがNotify処理を必要とする場合は、atnotify()を呼び出してコールバック関数を登録してください。
 *	  コールバック関数では、必要な通知コード以外にはすべてAPPNR_IGNOREを返してください。
 *	  たとえば、スタンバイを禁止し、それ以外の通知コードに対しては何もしないならば、次のようなコールバック関数を定義してください。
 *		int app_notify(int type, int param) {
 *			if(type == APPNF_STANDBY) {
 *				return APPNR_REJECT;
 *			}
 *			return APPNR_IGNORE; //APPNR_IGNORE=0なので、"return 0"でも構いません。
 *		}
 *		void app_init() {
 *			atnotify(app_notify);
 *		}
 *	* Sat Nov 27 13:52:00 JST 2004 Naoyuki Sawa
 *	- stream_stat()を追加しました。
 *	* Sat Dec 2 06:00:00 JST 2004 Naoyuki Sawa
 *	- TurboON/OFF両モード対応のpceTimerAdjustPrecisionCount()を追加しました。
 *	* Sun Dec 12 18:10:00 JST 2004 Naoyuki Sawa
 *	- stream_isrのスタック消費量を低減しました。変更理由は、以下の説明を読んでください。
 *	- ストリーム再生バッファを大きめに確保した場合、何重にも多重割り込みが発生して、スタックが溢れることがありました。(GYM Player MMC対応版作成中に発生)
 *	  stream_isrの実装において、これまでは初回割り込み・多重割り込みにかかわらず全レジスタ(%r0～%r15,%alr,%ahr)を退避・復元していましたが、
 *	  多重割り込み時のスタック消費量を低減するために、多重割り込み時は最低限のレジスタ(%r0,%r1)だけを退避・復元するように変更しました。
 *	  この変更により、ストリーム再生バッファを 320×3 サンプル程度確保してもスタックが溢れないことを確認しました。(GYM Player MMC対応版作成完了にて確認)
 *	* Sat Dec 18 17:00:00 JST 2004 Naoyuki Sawa
 *	- turboモード制御時の割り込み禁止/許可を、これまではturbo_CLOCK()の中だけDI～EIし、turbo_SRAM()とturbo_FLASH()は割り込み禁止をしていませんでした。
 *	  呼び出し元のturbo()関数内でまとめて割り込み禁止/許可を行うように変更しました。
 *	* Mon Dec 27 12:13:00 JST 2004 Naoyuki Sawa
 *	- スタックレイアウトの大幅な変更を行いました。
 *	  詳細は、clip.mkの2004/12/27のコメントを参照してください。
 *	- これまでは、システムスタックとユーザースタックは両方とも高速RAMに配置するか、
 *	  または両方ともSRAMに配置するしかありませんでした。
 *	  しかしながら、システムスタックはアプリケーションの動作にほとんど影響しないため、
 *	  システムスタックをSRAMに配置し、ユーザースタックを高速RAMに配置したいと考えました。
 *	  そこで、次のように仕様を変更しました。
 *	- システムスタックは常にSRAMに配置します。
 *	  SRAM上のシステムスタックサイズは、SYSSTACKにてバイト数を指示してください。
 *	  SYSSTACKを0とすることはできません。(起動時エラーとなります)
 *	- ユーザースタックは高速RAMまたはSRAMに配置します。
 *	- ユーザースタックをSRAMに配置する場合は、USRSTACKにてバイト数を指示してください。
 *	- ユーザースタックを高速RAMに配置する場合は、USRSTACKを定義しないか、または0としてください。
 *	  高速RAMの前半に高速RAM転送コードを配置した残りが、ユーザースタックとなります。
 *	  高速RAM転送コードが大きすぎて、ユーザースタック領域が小さすぎる場合は、起動時エラーとなります。
 *	* Wed Dec 29 05:00:00 JST 2004 Naoyuki Sawa
 *	- スタック情報をグローバル変数に直接定義していたのを変更し、STACKINFO構造体にまとめました。
 *	- スタック溢れ検査用の定数STACKCHECKをとりやめ、アドレス値で代用するよう変更しました。
 *	  PCからメモリダンプを取って目で見て眺めたときに、状況がわかり易いようにするためです。
 *	* Wed Jan 5 06:00:00 JST 2005 Naoyuki Sawa
 *	- internal_stream_callback()を汎用化し、stack_switch()として公開しました。
 *	  internal_stream_callback()とは引数順序が変わったので、注意してください。
 *	* Fri Jan 7 04:19:00 JST 2005 Naoyuki Sawa
 *	- FRAM4領域へのstack_switch()は頻繁に使うので、あらかじめ定義しておくことにしました。
 *	  呼び出し前後でスタックが溢れていないことも検査します。
 *	* Sun Jan 09 04:51:00 JST 2005 Naoyuki Sawa
 *	- stream_fill()でのbc--にpceSyncSafetyAddition()を利用できるよう、フィールドの型をchar→intへ変更しました。
 *	* Sun Feb  6 06:09:00 JST 2005 Naoyuki Sawa
 *	- 64ビット整数演算ライブラリは、きわめて使用頻度が低いので、
 *	  不要なアプリケーションにリンクされてサイズが無駄に増えるのを防ぐために、
 *	  clippce.c -> clipdi.cへ移しました。
 *	* Mon Mar  7 04:09:00 JST 2005 Naoyuki Sawa
 *	- turbo()関数に、FLASHとSRAMの出力ディセーブル時間変更機能を追加しました。
 *	  P/ECE Kernelは、FLASHとSRAMの出力ディセーブル時間を1.5サイクルに設定しますが、0.5サイクルで大丈夫です。
 *	  FLASHの出力ディセーブル時間は、仕様上20ns必要で、0.5サイクル(21ns)で充分です。
 *	  SRAMの出力ディセーブル時間は、仕様上25ns必要で、0.5サイクル(21ns)ではオーバークロックなのですけれど、
 *	  実際のプログラムで試してみた限りでは、動作するようです。(そもそも48MHzだってオーバークロックですし…)
 *	- なお、今回の変更により、turbo()関数の引数のmodeビット割り当てを変更しています。
 *	  これまでのビット割り当ては、
 *		D0: 24/48MHz
 *		D1: SRAM Wait
 *		D2: FLASH Wait
 *	  でしたが、変更後のビット割り当ては、
 *		D0: SRAM Wait
 *		D1: SRAM OutputDisable
 *		D2: FLASH Wait
 *		D3: FLASH OutputDisable
 *		D4: 24/48MHz
 *	  となりました。注意してください。
 *	* Sat Mar 26 07:24:00 JST 2005 Naoyuki Sawa
 *	- pceWave*()でストリーム再生を行うとき、バッファを64の倍数にしないと落ちる理由について、コメントを追加しました。
 *	  プログラムには変更ありません。
 *	* Tue Aug 23 18:31:00 JST 2005 Naoyuki Sawa
 *	- get_psr(),set_psr()を追加しました。
 *	* Sat Sep 03 21:40:00 JST 2005 Naoyuki Sawa
 *	- mmap()関数をマクロ化しました。
 *	  これまでは、mmap()関数がfile_defrag()を参照していたため、デフラグ機能が不要なアプリケーションにもclipdfrg.oがリンクされていました。
 *	  mmap()関数のマクロ化により、デフラグ機能が不要なアプリケーションにはclipdfrg.oがリンクされなくなり、2KB弱のメモリ節約となりました。
 *	* Sun Sep 04 09:56:00 JST 2005 Naoyuki Sawa
 *	- pceAppExit()にて、確実にタイマを停止する処理を追加しました。
 *	- この処理を追加した目的は、以下のとおりです。
 *	    1. アプリケーションがpceTimerSetCallback()を呼び出した、停止せずにそのまま終了してしまった場合の対策。
 *	    2. アプリケーションがtimer_start()を利用したことによる、pceTimerSetCallback()に対応するクリーンアップ。
 *	  1.は本当はアプリケーションの終了処理にて明示的に停止すべきですが、コーディングを簡単にするための手助けと考えてください。
 *	  2.は必須です。アプリケーションがtimer_start()を利用していない場合は、2.の目的に対してはダミー処理となります。
 *	- pceTimerSetCallback()の仕様では、ユーザープログラムはCh.0-3までの4チャネルを利用できることになっています。
 *	  現実には、カーネル1.20の実装では、Ch.0-5までの6チャネルが利用可能で、うち、Ch.5をカーネル自身がバッテリ情報更新に利用しています。
 *	  すなわち、ユーザープログラムはCh.0-4までの5チャネルを利用可能です。
 *	  pceAppExit()にてタイマを停止する処理は、Ch.0-4までを停止する考えもあります。(Ch.5を停止すると、バッテリ情報が更新されなくなります!)
 *	  しかし今回は、pceTimerSetCallback()の仕様に則って、ユーザープログラムに解放されている★Ch.0-3だけ★を停止することにしました。
 *	  もしも、アプリケーションにてUndoc'dなCh.4を利用する場合、Ch.4は自動的に停止しないので、アプリケーションにて明示的に停止してください。
 *	  (実際には、Ch.4は今後のカーネル拡張用の予備と考えられるため、アプリケーションによるCh.4の使用は控えるべきです。)
 *	* Sun Sep 04 16:30:00 JST 2005 Naoyuki Sawa
 *	- ユーティリティ：拡張タイマを追加しました。
 *	* Mon Sep 05 20:21:00 JST 2005 Naoyuki Sawa
 *	- ヒープマネージャAPIの排他制御を追加しました。
 *	  割り込みルーチン内でpceHeap*()APIが利用できるようになりました。
 *	* Thu Dec 01 12:42:00 JST 2002 Naoyuki Sawa
 *	- iscsym()/iscsymf()を追加しました。
 *	* Tue Jul 11 22:43:43 JST 2006 Naoyuki Sawa
 *	- P/ECE開発環境付属のstrstr()の不具合を回避するため、自前でstrstr()を用意しました。
 *	* Tue Jul 18 06:21:30 JST 2006 Naoyuki Sawa
 *	- sound_play()にループ回数指定を追加しました。
 *	* Tue Jul 18 23:23:40 JST 2006 Naoyuki Sawa
 *	- pceAppExit()にて、アプリケーション終了時のサウンド停止方法を、ソフト停止→ハード停止に変更しました。
 *	  このように変更した理由は、以下のとおりです。
 *	  アプリケーション終了時のサウンド停止方法にソフト停止を用いた場合、一定時間経過して自動的にハード停止となる前に、
 *	  次のアプリケーションを起動すると、ノイズが鳴ることがあったからです。
 *	  ノイズの原因は、恐らくアンプ回路などのアナログ的なものであり、カーネルやライブラリの不具合では無いと思いますが、
 *	  やや耳障りであることは否めません。
 *	  そこで、アプリケーション終了時のサウンド停止方法を、ソフト停止→ハード停止に変更しました。
 *	  これまで、特別な理由があってソフト停止を選択していたわけではなく、今回の変更によるデメリットは何も無いはずです。
 *	* Mon Jan 08 01:00:26 JST 2007 Naoyuki Sawa
 *	- pceAppInit()に、高速除算ルーチンの初期化(idiv_init())を追加しました。
 *	* Thu Mar 01 01:53:43 JST 2007 Naoyuki Sawa
 *	- sound_stat()を追加しました。
 *	* Sun Apr 22 21:28:18 JST 2007 Naoyuki Sawa
 *	- pow()の置き換え用実装を修正しました。
 *	* Thu Sep 27 15:54:04 JST 2007 Naoyuki Sawa
 *	- _render_object()をASM化して、高速化しました。
 *	  C言語版では、「pceLCDDrawObject(obj)」呼び出し時に、無駄なmemcpy()が生成されてしまっていました。
 *	  2007/09/27 ASM版の主な高速化は、上述の無駄なmemcpy()を省いたことです。
 *	* Thu Oct 25 14:45:01 JST 2007 Naoyuki Sawa
 *	- Aquaplus版よりも高速な、memcpy()を作成しました。
 *	- Aquaplus版よりも高速な、memset()を作成しました。
 *	* Mon Dec 10 14:36:46 JST 2007 Naoyuki Sawa
 *	- atexit()に複数の関数を登録できるよう変更しました。
 *	  これまでは、一つしか登録できませんでした。
 *	- 今回の変更では、登録できる上限数は4固定です。
 *	  5以上登録しようとすると、エラーメッセージを表示して終了します。
 *	  一般的なアプリケーションでは4つで充分だと思いますが、もし不足になったら、exit_func[]の配列数を変更してください。
 *	- ANSI規格のatexit()では、登録バッファを動的に拡張し、上限数は無限ですが、今回はそうしませんでした。
 *	  このような重要でない部分でむやみに処理を複雑にし、実行ファイルサイズが増加するのを避けるためです。
 *	  上述の通り、一般的なアプリケーションでは、上限数固定で充分だと思います。
 *	* Sun Dec 30 15:29:56 JST 2007 Naoyuki Sawa
 *	- 高精度パッドセンシングを追加しました。
 *	* Mon Dec 31 16:36:08 JST 2007 Naoyuki Sawa
 *	- アプリケーションパッドセンシングを追加しました。
 *	* Tue Jan 08 12:39:27 JST 2008 Naoyuki Sawa
 *	- pceAppInit()の中のidiv_init()呼び出しを、cstart.cのpceAppInit00()へ移動しました。
 *	* Tue Jan 08 14:58:19 JST 2008 Naoyuki Sawa
 *	- memcpy()を、ベクタ領域のFRAMに転送して実行するために、clipidiv.sへ移動しました。
 *	- memset()を、ベクタ領域のFRAMに転送して実行するために、clipidiv.sへ移動しました。
 *	* Sat Sep 13 15:22:47 JST 2008 Naoyuki Sawa
 *	- pad_get()の挙動を改善しました。
 *	* Thu Mar 12 22:36:44 JST 2009 Naoyuki Sawa
 *	- atexit()で登録した関数のコールバック中からも、さらにatexit()で関数を登録できるよう変更しました。(pceAppExit()関数内の処理変更)
 *	  これまでは、atexit()で登録した関数のコールバック中でatexit()を使うと、不正な動作となっていました。
 *	  今後は、atexit()で登録した関数のコールバック中でatexit()を使うと、その関数から戻った次に、新たに登録した関数が呼び出されます。
 *	  たとえば以下のコードの場合、main()→foo()→bar()の順に呼び出されます。
 *		int main() { atexit(foo); return 0; }
 *		void foo() { atexit(bar); }
 *		void bar() { }
 *	* Mon Mar 30 19:21:12 JST 2009 Naoyuki Sawa
 *	- PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチを適用しました。
 *	  詳しくは、realloc()関数内のコメントを参照してください。
 *	* Tue Mar 31 15:01:38 JST 2009 Naoyuki Sawa
 *	- コードサイズを節約するために、PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチの方法を変更しました。
 *	* Sun Jun 21 21:42:18 JST 2009 Naoyuki Sawa
 *	- 今回のpceAppProc()の開始時刻を記録する、apptm_wk変数を追加しました。
 *	  変数名は、/usr/PIECE/sysdev/pcekn/runapp.cで使われている、同じ名前・同じ用途のカーネル変数に由来しています。
 *	- 2009/06/21現在、clipidle.cモジュールで、残りタイムスライスを算出するために利用しています。
 *	* Thu Jun 25 23:30:29 JST 2009 Naoyuki Sawa
 *	- render_string()を、CR/LF/CR+LF対応しました。
 *	  改行ありの指定を行った場合、CR(\r)、LF(\n)、CR+LF(\r\n)のどれでも、ひとつの改行として扱います。
 *	  これまでは、LF(\n)だけを改行として扱い、CR(\r)は改行せずにゴミが表示されていました。
 *	* Sat Jul 04 21:25:58 JST 2009 Naoyuki Sawa
 *	- lfind()、lsearch()を追加しました。
 *	* Mon Jul 06 23:33:17 JST 2009 Naoyuki Sawa
 *	- strupr()、strlwr()を追加しました。
 *	* Thu Jul 09 14:23:04 JST 2009 Naoyuki Sawa
 *	- splitpath()、makepath()を追加しました。
 *	* Wed Jul 15 14:39:47 JST 2009 Naoyuki Sawa
 *	- render_font_framed_param[]を追加しました。
 *	- render_font_framed()とrender_string_framed()を、コードサイズ低減のために、render_font_framed_param[]を使うように変更しました。
 *	* Sat Dec 25 12:48:38 JST 2010 Naoyuki Sawa
 *	- strupr()やstrlwr()で、全角文字を含む文字列を変換すると、文字化けすることがある問題を修正しました。
 *	* Sun Apr 17 02:15:29 JST 2011 Naoyuki Sawa
 *	- 4x6ドットフォントの半角カナ拡張を追加しました。
 *	  4x6ドットフォントで半角カナが使用できるようになりました。
 *	* Sat Apr 23 23:50:34 JST 2011 Naoyuki Sawa
 *	- new_pceFontGetAdrs()をアセンブラ化しました。
 *	* Tue Apr 26 23:35:50 JST 2011 Naoyuki Sawa
 *	- new_pceFontSetType()をアセンブラ化しました。
 *	* Sun Mar 11 22:22:46 JST 2012 Naoyuki Sawa
 *	- EPSONライブラリのmemmoveを置き換えました。
 *	* Fri Apr 20 11:09:47 JST 2012 Naoyuki Sawa
 *	- strcasecmp()とstrncasecmp()をアセンブラ化しました。
 *	- iscsym()とiscsymf()をアセンブラ化しました。
 *	* Wed Oct 03 23:51:13 JST 2012 Naoyuki Sawa
 *	- isblank()を追加しました｡
 *	* Sat Oct 20 20:15:27 JST 2012 Naoyuki Sawa
 *	- ffs()を追加しました。
 *	* Sun Nov 18 13:59:36 JST 2012 Naoyuki Sawa
 *	- strnlen()とstrndup()を追加しました。
 *	- strlen()とstrnlen()をまとめて、アセンブラ化しました。
 *	- strdup()とstrndup()をまとめて、アセンブラ化しました。
 *	* Sat Dec 15 14:14:55 JST 2012 Naoyuki Sawa
 *	- strcmp()とstrncmp()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
 *	- strchr()とstrrchr()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
 *	* Tue Dec 18 21:23:57 JST 2012 Naoyuki Sawa
 *	- memchr()とmemcmp()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
 *	* Fri Dec 21 00:22:01 JST 2012 Naoyuki Sawa
 *	- strspn()とstrcspn()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
 *	* Wed Mar 27 20:21:54 JST 2013 Naoyuki Sawa
 *	- strsep()を追加しました。宣言は、include/string.hに追加しました。
 *	- strpbrk()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
 *	* Mon Jul 15 20:32:11 JST 2013 Naoyuki Sawa
 *	- sound_att()を追加しました。
 *	* Mon Jan 13 19:05:18 JST 2014 Naoyuki Sawa
 *	- lfind()とlsearch()の宣言を、clippce.h⇒include/search.hへ分離しました。
 *	- lfind()とlsearch()の実装を、clippce.c⇒clipsrch.cへ分離しました。
 *	* Sat Aug 16 15:46:20 JST 2014 Naoyuki Sawa
 *	- get_psr(),set_psr()をmachine.hで実装し、当モジュールの実装は削除しました。
 *	- ストリームコールバック処理中にタイマ割り込みが発生しないよう修正しました。
 *	* Fri Sep 05 21:36:08 JST 2014 Naoyuki Sawa
 *	- ファイル書き込み系関数の排他制御を追加しました。
 *	  詳細は、「ファイル書き込み系関数の排他制御」のコメントを参照してください。
 *	* Sat Sep 20 08:44:17 JST 2014 Naoyuki Sawa
 *	- FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
 *	  詳細は、clippce.cの「ファイル書き込み系関数の排他制御」のコメントと「FRAM4領域の退避復元」を参照してください。
 *	* Sat Sep 20 16:19:04 JST 2014 Naoyuki Sawa
 *	- USB割込みとタイマ割込みの割込み優先度を下げました。
 *	  詳細は、lower_restore_interrupt_priority()のコメントを参照してください。
 *	* Mon Sep 22 00:22:24 JST 2014 Naoyuki Sawa
 *	- ストリームフィルタを追加しました。
 *	- 使用目的は、clipaudi.cモジュールの機能と組合せて、展開したサウンドデータをUSB Audio Deviceへ送り込むことです。
 *	  ただし、上記の使用目的に限らず、展開したサウンドデータをさらに変更したり、別の処理に送る用途にも使えそうです。
 *	* Mon Sep 29 23:30:57 JST 2014 Naoyuki Sawa
 *	- malloc_usable_size()を追加しました。
 *	* Thu Oct 09 22:33:48 JST 2014 Naoyuki Sawa
 *	- strcat()とstrncat()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
 *	* Sun Oct 12 18:49:43 JST 2014 Naoyuki Sawa
 *	- strstr()がs1がヌル終端されていなくても動作するように修正しました。s2はヌル終端されていなければなりません。
 *	- strcasestr()を追加しました。宣言はinclude/string.hに追加しました。strcasestr()はGNU拡張です。
 *	* Fri Oct 31 00:45:36 JST 2014 Naoyuki Sawa
 *	- strlcpy()を追加しました。宣言は、include/string.hに追加しました。
 *	- strlcat()を追加しました。宣言は、include/string.hに追加しました。
 *	* Tue Nov 04 23:07:44 JST 2014 Naoyuki Sawa
 *	- pdwait()を追加しました。
 *	* Sun Dec 07 14:41:16 JST 2014 Naoyuki Sawa
 *	- pceHeapAlloc()の性能改善を行いました。
 *	* Fri Dec 19 21:35:23 JST 2014 Naoyuki Sawa
 *	- stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。
 *	  ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。
 *	  なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
 *	* Fri Dec 19 22:06:53 JST 2014 Naoyuki Sawa
 *	- 割り込みレベル設定を追加しました。
 *	- stream_fill()が、割り込みレベル設定の関数を利用するように変更しました。動作内容はこれまでと変わりません。
 *	* Fri Dec 19 22:30:18 JST 2014 Naoyuki Sawa
 *	- ヒープマネージャAPIの排他制御を、ENTER_CS/LEAVE_CS⇒ENTER_IL(1)/LEAVE_ILに変更しました。
 *	- ファイル書き込み系関数の排他制御を、ENTER_CS/LEAVE_CS⇒ENTER_IL(1)/LEAVE_ILに変更しました。
 *	* Mon Dec 22 21:06:44 JST 2014 Naoyuki Sawa
 *	- 「ヒープマネージャAPIが排他制御されない問題」(Fri Dec 19 22:30:18 JST 2014)を回避するために、STREAMINFO.busyを廃止し、擬似的な割り込みレベル1を用いてstream_fill()を実行するように変更しました。
 *	- stream_fill()内の割り込みレベル設定処理を削除しました。
 *	  stream_fill()は、stream_play()内のENTER_CS～LEAVE_CS区間,及び,hdm0_isr()内の(IL=1,IE=1)区間からしか呼ばれなくなったので、割り込みレベルを設定しなくても既に(IL≧1)の要件を満たしているからです。
 *	* Tue Dec 23 15:42:08 JST 2014 Naoyuki Sawa
 *	- 高速RAMにコードを転送する処理を、clippce.cのapp_init()⇒cstart.cのpceAppInit00()へ移動しました。
 *	  app_init()で行っても問題無いですが、より早くpceAppInit00()で行う方がより確実だと思ったからです。
 *	* Wed Dec 24 19:14:00 JST 2014 Naoyuki Sawa
 *	- 「タイマ割り込み処理中の多重割り込み許可」を追加しました。
 *	* Wed Dec 24 20:09:42 JST 2014 Naoyuki Sawa
 *	 - stream_isr:を3命令節約しました。
 *	* Thu Dec 25 00:13:22 JST 2014 Naoyuki Sawa
 *	- stream_play()の第3引数をfram4⇒flagsに変更しました。
 *	* Fri Dec 26 21:45:54 JST 2014 Naoyuki Sawa
 *	- 「8ビットプログラマブルタイマ割り込みの一元管理」を追加しました。
 *	* Sat Dec 27 14:10:18 JST 2014 Naoyuki Sawa
 *	- 高精度パッドセンシングが「8ビットプログラマブルタイマ割り込みの一元管理」を使用するように変更しました。
 *	* Sat Dec 27 17:13:55 JST 2014 Naoyuki Sawa
 *	- internal_8tu_isr()と_internal_8tu_isr()を統合して、internal_8tu_isr()一関数にし、最適化しました。
 *	* Sat Dec 27 21:06:26 JST 2014 Naoyuki Sawa
 *	- 割り込みサービスルーチンの無駄なレジスタ保存を削除しました。
 *	* Sun Dec 28 13:06:59 JST 2014 Naoyuki Sawa
 *	- 拡張タイマテーブルを、固定割り当て⇒動的割り当てに変更しました。
 *	  拡張タイマを少ししか使わないアプリケーションにとっては、メモリを節約できるメリットが有ります。
 *	  拡張タイマをたくさん使うアプリケーションにとっては、使用数の制限が無くなるメリットが有ります。
 *	- 拡張タイマを使っている、既存アプリケーションに対する影響は無いので、修正する必要は有りません。
 *	* Wed Dec 31 00:00:35 JST 2014 Naoyuki Sawa
 *	- P/ECEカーネルのNMIサービスルーチンをフックして高速化するルーチンを作成しました。
 *	  効果が薄いのでこの方法は使わないことにしましたが、コメントとして残しておきます。
 *	* Wed Dec 31 17:37:43 JST 2014 Naoyuki Sawa
 *	- cstart.cのpceAppInit00()でpceLCDDispStart()を呼び出し、液晶画面の表示を開始するようにしました。
 *	  この変更に伴い、stack_layout(),stack_check()内のpceLCDDispStart()呼び出しは不要になり、削除しました。
 *	* Sun Jan 04 01:02:41 JST 2015 Naoyuki Sawa
 *	- 「高精度パッドセンシング(入力割り込み版)」を追加しました。
 *	  USE_INPUT_INTERRUPT_PRECISION_PADシンボルによって、既存の「高精度パッドセンシング(タイマ割り込み版)」と「高精度パッドセンシング(入力割り込み版)」のどちらを使用するかを選択できます。
 *	  2015/01/04現在は、動作検証のために「高精度パッドセンシング(入力割り込み版)」を使用するようにしてあります。
 *	  もし動作検証の結果、問題が見つかった場合は、USE_INPUT_INTERRUPT_PRECISION_PADをコメントアウトして、既存の「高精度パッドセンシング(タイマ割り込み版)」を使うように戻すかも知れません。
 *	* Sun Jan 04 13:35:55 JST 2015 Naoyuki Sawa
 *	- 「割り込みサービスルーチンを定義するマクロ」を追加しました。
 *	- ISR_DEF()マクロを使って割り込みサービスルーチンを定義すると、以下の利点が有ります。
 *	  1. C関数だけで割り込みサービスルーチンを定義できます。明示的にasmブロックを書く必要が有りません。
 *	  2. レジスタ退避処理が共通処理になり、コードサイズを節約できます。
 *	* Wed Jan 07 21:18:08 JST 2015 Naoyuki Sawa
 *	- errnoを、clippce.c⇒cliplibc.cへ移動しました。
 *	- 「下位レベル関数/ダミー関数 : 標準Ｃライブラリ」の実装を、clippce.c⇒cliplibc.cへ移動しました。
 *	- 「EPSONライブラリの置き換え」の実装を、clippce.c⇒cliplibc.cへ移動しました。
 *	- 「標準Ｃライブラリの補助」の実装を、clippce.c⇒cliplibc.cへ移動しました。具体的には、clippce.hの2015/01/07の下記コメントに相当します:
 *	  │ - strdup(),strcasecmp(),strncasecmp()の宣言をclippce.h⇒include/string.hへ，実装をclippce.c⇒cliplibc.cへ移動しました。
 *	  │   strcasecmp(),strncasecmp()は本当はstrings.hで宣言すべきなのですが、P/ECEにはstrings.hが無いのでinclude/string.hで宣言することにしました。
 *	  │ - strnlen(),strndup()の宣言をclippce.h⇒include/string.hへ，実装をclippce.c⇒cliplibc.cへ移動しました。
 *	  │ - ismbblead(),ismbbtrail(),iscsym(),iscsymf(),isblank()の宣言をclippce.h⇒include/ctype.hへ，実装をclippce.c⇒cliplibc.cへ移動しました。
 *	  │   ismbblead(),ismbbtrail()は本当はmbctype.hで宣言すべきなのですが、P/ECEにはmbctype.hが無いのでinclude/ctype.hで宣言することにしました。
 *	  │ - strupr(),strlwr()の宣言をclippce.h⇒include/string.hへ，実装をclippce.c⇒cliplibc.cへ移動しました。
 *	  │ - splitpath(),makepath()の宣言をclippce.h⇒include/stdlib.hへ，実装をclippce.c⇒cliplibc.cへ移動しました。
 *	  │ - ffs()の宣言をclippce.h⇒include/string.hへ，実装をclippce.c⇒cliplibc.cへ移動しました。
 *	  │   ffs()は本当はstrings.hで宣言すべきなのですが、P/ECEにはstrings.hが無いのでinclude/string.hで宣言することにしました。
 *	  │ - fileno(),isatty()の宣言を、clippce.h⇒include/stdio.hへ移動しました。実装はclipio.cにあります。
 *	  │   isatty()は本当はio.hかunistd.hで宣言すべきなのですが、P/ECEにはio.hもunistd.hも無いのでinclude/stdio.hで宣言することにしました。
 *	* Fri Jan 09 21:26:07 JST 2015 Naoyuki Sawa
 *	- 内蔵I/Oメモリのミラー空間を使って、_precision_pad_isr()の命令数を削減しました。
 *	* Sun Feb 01 03:00:42 JST 2015 Naoyuki Sawa
 *	- 「4x6ドットフォントの半角カナ拡張」を省メモリ化しました。
 *	  詳細は、USE_VOLATILE_FONTを定義している箇所のコメントを参照してください。
 *	* Sun Feb 01 20:44:18 JST 2015 Naoyuki Sawa
 *	- hook_unhook_timer()のバグが原因でアプリケーション起動時に数%の確率でハングアップしていた問題を修正しました。
 *	  pceVectorSetTrap()の戻り値をold_timer_isrに格納する前に割込みが掛かるとnew_timer_isrが未格納のold_timer_isrを参照していたバグでした。
 *	  正しくはpceVectorSetTrap()の戻り値をold_timer_isrに格納するまで割り込み禁止にすべきでした。
 *	* Sat Mar 14 21:29:01 JST 2015 Naoyuki Sawa
 *	- pceAppExit()の中にfcloseall()の呼び出しを追加しました。
 *	* Wed Mar 18 22:26:03 JST 2015 Naoyuki Sawa
 *	- main()を呼び出す方法を変更しました。
 *	  これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。
 *	  この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
 *	* Tue May 26 21:15:26 JST 2015 Naoyuki Sawa
 *	- 4x5ピクセルフォントを追加しました。
 *	- P/ECEのフォントは2番までですが、pceFontGetAdrs()とpceFontSetType()をフックする事により、3番に4x5ピクセルフォントを追加しました。
 *	  主にrender_printf()等で使用する事を想定していますが、上記のAPIをフックしているので、pceFontPrintf()等の標準APIでも使用可能です。
 *	- 4x5ピクセルフォントには、「Version 0」(http://ver0.sakura.ne.jp/)さんの『極小フォント』(http://ver0.sakura.ne.jp/illust/extra-small-font.html)を使用させて頂きました。
 *	  それに伴い、4x6ピクセルフォントも『極小フォント』を使うように変更しました。これまで4x6ピクセルフォントは「WentWayUp: WebLog (http://wentwayup.tamaliver.jp/)」さんの、
 *	  『KanaFive(http://wentwayup.tamaliver.jp/e59624.html)』を使用させて頂いていたのですが、4x5ピクセルフォントと4x6ピクセルフォントの書体を統一するためです。
 *	- 今回使用させて頂いた『極小フォント』を、/clip/keep/極小フォント.7zに保存しておきました。
 *	- これまでは、USE_KANAFIVE_ASCIIとUSE_VOLATILE_FONTで機能の有効,無効を切り替えてコンパイルするようにしていましたが、今回の変更と同時にこれらの機能を有効に固定しました。
 *	  これまで使って来て、これらの機能も有用で、今後、無効にする必要は無いと判断したからです。
 *	* Wed Sep 16 01:23:07 JST 2015 Naoyuki Sawa
 *	- timer_proc()が、経過時間に応じた処理回数分まとめて処理するように、変更しました。
 *	- このように変更したきっかけは、ストリームコールバックや標準エラー出力(USB)を使うと、結構タイマ割り込みが飛ぶ事に気付いたからです。(ストリームコールバックと標準エラー出力(USB)のどちらが主な要因かは未調査です。)
 *	  例えば、TAPシーケンサはタイマで再生時間のを計測して自然ループや自然停止のコールバックを行うのですが、実際に音の再生がループするよりも自然ループのコールバックがだんだん遅れる事に気付きました。
 *	  音の再生はミキシング割り込み(DMA割り込み)駆動であり、タイマ割り込みよりも正確であるはずなので、タイマ割り込みの方が遅れているのだと判断しました。
 *	- おそらく、ミキシング割り込みが1[ms]以上かかった時に、タイマ割り込みが飛んでいるのだと思いました。
 *	  そこで、タイマ割り込みが飛んだ後は、経過時間に応じた処理回数分まとめて処理する事にしました。
 *	  この修正を行った後は、TAPシーケンサの自然ループのコールバックが遅れなくなった事を、動作確認しました。
 *	- 本来この修正は、timer_proc()で対応するよりも、タイマ割り込みそのもの(timer_isr())で行う方が望ましいのですが、出来ませんでした。
 *	  timer_isr()は、スタックフレームの構成を仕様化しているため、経過時間に応じた処理回数分まとめて処理するためのループ処理を入れられないからです。
 *	  当ソースの後ろの方に有る、new_timer_isrの実装を見れば、その中にループを入れられない事が理解出来ると思います。
 *	* Wed Nov 18 21:37:53 JST 2015 Naoyuki Sawa
 *	- 「ユーティリティ：拡張タイマ」を、(clippce.h,clippce.c)⇒(cliptimr.h,cliptimr.c)へ移動しました。
 *	  移動する事にした理由は、timer_start()とtimer_stop()を、Win32にも対応したからです。
 *	  (clippce.h,clippce.c)はP/ECE専用でありWin32プロジェクトには含められないので、別のファイルへ分離しました。
 *	* Tue Nov 24 21:41:25 JST 2015 Naoyuki Sawa
 *	- unistd.h,cliplibc.cにてPOSIX仕様のusleep()を実装した事に伴い、既存のclippce.h,clippce.cの独自仕様のusleep()を削除しました。
 *	* Thu Mar 03 21:33:11 JST 2016 Naoyuki Sawa
 *	- pad_get()内の無駄な処理を削除しました。
 *	  これまで、'if(trg&mask){'が否定された時しか通らない処理の中で'trg&=~mask'を行っていました。
 *	  'if(trg&mask){'が否定されたという事は必ず'!(trg&mask)'ですから、'trg&=~mask'を行う事は(害はないが)無駄でした。
 *	* Thu Jun 02 21:20:46 JST 2016 Naoyuki Sawa
 *	- 「main()を呼び出す前に初期化関数を実行する仕組み」を追加しました。
 *	* Fri Jun 03 21:47:06 JST 2016 Naoyuki Sawa
 *	- 「main()が終了した後に終了時関数を実行する仕組み」を追加しました。
 *	* Fri Jul 15 21:06:38 JST 2016 Naoyuki Sawa
 *	- ヌルレンダラーの定数データ「null_render」を定義しました。
 *	  実際の描画を行わずに、描画関数の戻り値だけを取得したい場合に、空の描画対象として指定するために、汎用的に利用出来ます。
 *	  現在の所、ヌルレンダラーを指定する意味が有ると思う関数は、文字列描画関数だけです。
 *	  文字列描画関数は、戻り値として描画した文字列のサイズを返すので、実際に描画せずに、文字列のサイズだけを取得するために、ヌルレンダラーを指定すると便利です。
 *	  具体的には、下記の関数に対して、ヌルレンダラーを指定する意味が有ると思います。
 *	  ・render_font(),render_string(),render_printf(),render_font_framed(),render_string_framed(),render_printf_framed()
 *	  ・render_font7(),render_string7(),render_printf7(),render_font7_framed(),render_string7_framed(),render_printf7_framed()
 *	  ・render_font8(),render_string8(),render_printf8(),render_font8_framed(),render_string8_framed(),render_printf8_framed()	下記のrender_object()に関する説明を参照せよ。
 *	  ・render_fontx(),render_stringx(),render_printfx(),render_fontx_framed(),render_stringx_framed(),render_printfx_framed()	下記のrender_object()に関する説明を参照せよ。
 *	- RENDER構造体のフィールドを変更する関数には、null_renderを引き渡さないように注意して下さい。
 *	  例えば、render_save(),render_restore()等には、null_renderを引き渡さないように注意して下さい。
 *	  (もとより、render_save(),render_restore()は戻り値が無いので、ヌルレンダラーを指定する事自体が無意味であり、これらの関数にヌルレンダラーを引き渡す事は無いと思いまずが。)
 *	  ただし特例として、render_object()に対してヌルレンダラーを指定する事は、(厳密には良くないのですが)、動作上は問題ありません。
 *	  render_object()は、一時的にnull_render.context->textureを書き換えますが、元に戻すからです。
 *	  例えば、render_printf8()等にnull_renderを指定した場合、間接的にrender_object()が呼び出され、render_object()に対してnull_renderが引き渡されますが、正常に動作します。
 *	- ヌルレンダラーの使用例は、以下の通りです。
 *	  <例>
 *	  │//文字列の中心が目標位置になるように、文字列を描画するサンプル
 *	  │void test() {
 *	  │  int xy, x, y;
 *	  │  //目標位置を示すためのクロスハッチを描画する。
 *	  │  x = DISP_X / 2;
 *	  │  y = DISP_Y / 2;
 *	  │  render_line(&render, 0, y, DISP_X, y, 1);
 *	  │  render_line(&render, x, 0, x, DISP_Y, 1);
 *	  │  //文字列の描画サイズを取得するために、ヌルレンダラーへのダミー描画を行う。
 *	  │  xy = render_printf(&null_render, 0, 0, 0, 0, "日本語\n数値 %'d\nAB12", (now * now));
 *	  │  //                               ~~~~     ~ null_renderへ描画する時の、x,y,colorは何でも構いません。
 *	  │  //文字列の描画サイズから、文字列の中心を目標位置として描画するための、描画原点座標を求める。
 *	  │  //ここの計算式を変えれば、文字列の右下を目標位置としたり、文字列の左下を目標位置とする事も可能です。
 *	  │  x -= LOHALF(xy) / 2;
 *	  │  y -= HIHALF(xy) / 2;
 *	  │  //文字列を描画する。
 *	  │  render_printf(&render, x, y, 0, 3, "日本語\n数値 %'d\nAB12", (now * now));
 *	  │}
 *	  或いは、ヌルレンダラーを使わなくても、クリッピング領域を狭めたり、サーフェイス外を指定する事で、実際に描画せずに、文字列のサイズだけを取得する事は可能ではあるのですが、
 *	  ヌルレンダラーを指定する方が、意図が明確に判り、見た目に判り易いソースコードになると思います。
 *	- ヌルレンダラーの定義においては、極力、メモリを節約するように工夫しました。
 *	  もし工夫せずに、RENDER構造体とSURFACE構造体を定義すると、(24+52*8)+12=452バイトも消費してしまいます。
 *	  今回は、以下の工夫を行って、28バイトしか消費しないようにしました。
 *	  工夫1. RENDER.context_stack[]は1要素分しか使用しないので、定数データに含めませんでした。
 *	  工夫2. 1要素分のコンテキストは、RENDER.context_stack[0]を指すのではなく、下記の工夫3で重複させた領域を指すようにしました。
 *	  工夫3. RENDER構造体と、SURFACE構造体の、値が共通な部分を重複させて定義しました。
 *	  工夫4. 1要素分のコンテキストスタックの行列部分の値は不定値で構わないので、別のコード領域に重複させて、textureフィールドだけが確実にNULLの値を指すようにしました。
 *	  詳細は、clippce.c内のnull_renderを定義しているアセンブラブロックを参照して下さい。
 *	* Thu Nov 17 21:56:29 JST 2016 Naoyuki Sawa
 *	- assert()マクロの実装を変更しました。(変更したファイルは、clippce.cとinclude/assert.hです。)
 *	  詳細は、clippce.c内のnew_assert_isr()の実装部のコメントを参照して下さい。
 *	* Fri Dec 02 22:30:56 JST 2016 Naoyuki Sawa
 *	- assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
 *	  変更した理由の詳細は、clippce.c内の「assert()マクロのサポートルーチン」の同日のコメントを参照して下さい。
 */
#include "clip.h"

/*****************************************************************************
 *	定数・マクロ
 *****************************************************************************/

/* 高速RAMの空きエリアアドレスを表すシンボル（リンカが自動生成）  */
extern unsigned long __START_FRAM_ADDR[];	/* 転送先アドレス */
extern unsigned long __SIZEOF_FRAM_ADDR[];	/* 転送先サイズ[byte]（転送元シンボルでは全体サイズの取得不可！) */
extern unsigned long __START_framtop_code[];	/* 転送元アドレス */
extern unsigned long __END_DEFAULT_BSS[];	/* システムスタック先頭アドレス（==BSS領域終端アドレス） */

///* 高速RAMへのコードのコピー/クリア (\usr\PIECE\pcekn\pcekn.hより)
// * * Tue Nov 19 12:30:00 JST 2002 Naoyuki Sawa
// * - これまで、clippce.hで定義していましたが、2002/11/19以降、
// *   CLiPライブラリが高速RAMを占有するようになったので、
// *   うっかりアプリケーションが高速RAMにコード転送を行ってしまわないよう、
// *   アプリケーションから見えないところへ移動しました。
// *   プログラム全体で、FRAMCOPYを使うのはapp_init()だけです。
// *   (FRAMCLEARは今のところ誰も使っていません)
// */
//#define FRAMCOPY(dstsect,srcsect)\
//{\
//extern unsigned long __START_##dstsect[];\
//extern unsigned long __START_##srcsect[];\
//extern unsigned long __SIZEOF_##srcsect[];\
//	memcpy(\
//		&__START_##dstsect,\
//		&__START_##srcsect,\
//		(unsigned)&__SIZEOF_##srcsect\
//	);\
//}
//#define FRAMCLEAR(dstsect,srcsect)\
//{\
//extern unsigned long __START_##dstsect[];\
//extern unsigned long __SIZEOF_##srcsect[];\
//	memset(\
//		&__START_##dstsect,\
//		0,\
//		(unsigned)&__SIZEOF_##srcsect\
//	);\
//}
//→2003/04/01 clipfram.c分割に伴い、この方法では転送サイズが判断できなくなりました。
//             マクロを使わず、直接書くことにしました。（app_init()内のコード参照）

///* スタックあふれ検査のために、スタックの底１ワードに書き込んでおく定数です。
// * また、スタック使用量検出のための乱数値書き込みのシードとしても利用します。
// */
//#define STACKCHECK	0xdf9b5713
//→2004/12/29 スタック検査用の定数を取りやめ、アドレス値で代用するように変更しました。
//             たとえば、0x123456番地には0x123456という値を書き込み、あとで検査します。

/*****************************************************************************
 *	グローバル変数
 *****************************************************************************/

//{{2015/01/07削除:errnoを、clippce.c⇒cliplibc.cへ移動しました。
///* S1C33 Family Cコンパイラパッケージでは、errnoはアプリケーション側で定義する必要があります。
// * [S5U1C33000C_J.pdf p.140] */
//int errno;
//}}2015/01/07削除:errnoを、clippce.c⇒cliplibc.cへ移動しました。

/*****************************************************************************
 *	ローカル変数
 *****************************************************************************/

//{{2007/12/10:atexit()に複数の関数を登録できるよう変更しました。
///* プログラム終了時に実行する関数へのポインタ。
// * atexit()で登録します。(現在の実装では一度のみです。)
// * NULLなら、まだ登録されていません。
// */
//static void (*exit_func)();
//↓2007/12/10:atexit()に複数の関数を登録できるよう変更しました。
static void (*exit_func[4/*調整可*/])();
//}}2007/12/10:atexit()に複数の関数を登録できるよう変更しました。

/* プログラム終了時に実行する関数へのポインタ。
 * atnotify()で登録します。(現在の実装では一度のみです。)
 * NULLなら、まだ登録されていません。
 */
static int (*notify_func)(int type, int param);

///* * 空きメモリの大部分をmalloc()用ヒープとして確保します。
// *   アプリケーションからのメモリ確保は、pceHeapAlloc()ではなくmalloc()を使ってください。
// * * pceFile系APIが内部でpceHeapAlloc()を使い、4～8KB程度のメモリ確保を行うので、
// *   システム用ヒープ領域として、少しだけ空きメモリを残しておく必要があります。
// * * BIOS1.18現在、pceFileLoad()が(0x2000-32)バイトのメモリ確保を行うのが最大のようですが、
// *   実際には、アプリケーションコンテキストからpceFileLoad()が直接呼ばれることはありません。
// *   pceFileLoad()が呼ばれる手順は次のとおりです：(runapp.c内のAppStart()関数を参照)
// *	1. pceAppExecFile() … 実行ファイル名を格納し、アプリケーション終了要求
// *	2. AppLoop()から抜ける … アプリケーション終了
// *	3. ResetHeap(INITAPPEXTBUFF) … ヒープ領域を0x13e000～0x13ffffに設定
// *	4. search_app() -> pceFileLoad() … ヒープ領域から(0x2000-32)バイトのメモリ確保、
// *	                                    このメモリを使って実行ファイルを展開する。
// *	5. InitHeapAndSP() … ヒープ領域をアプリケーション末尾～0x13ffffに設定
// *	6. AppInit(),AppLoop() … アプリケーション実行開始
// *   このように、pceFileLoad()が呼ばれるとき、アプリケーションは既に終了しています。
// *   アプリケーション終了後、ヒープ領域は強制的に0x13e000～0x13ffffに再設定されます。
// *   従って、pceFileLoad()が確保する分のメモリを残しておく必要はありません。
// * * pceFileCreate/Rename/Delete()が、4096バイト(=BLOCKSIZE)のメモリ確保を行います。
// *   BIOS1.18現在、アプリケーションコンテキストでメモリ確保を行うAPIは、この3つだけです。
// *   実験したところ、(確保するメモリ＋管理領域)の空きメモリがあれば、割り当て成功します。
// *   以上より、残しておかなければいけない空きメモリの最小サイズは：
// *	BLOCKSIZE + sizeof(HEAPMEM)
// *   となります。
// * * 今後のBIOS更新によって、もっと大きなメモリ確保を行うAPIが追加された場合は、
// *   HEAPMARGINを調整する必要があります。
// */
//#define HEAPMARGIN	(BLOCKSIZE + sizeof(HEAPMEM))	/* システム用ヒープ領域サイズ */
//static void* heap;					/* malloc()用ヒープ領域       */
//→* Thu Jun 17 05:14:00 JST 2004 Naoyuki Sawa
//→  メモリ管理を、EPSONライブラリmalloc()から、P/ECE API pceHeapAlloc()に変更しました。
//→  変更に伴って、HEAPMARGINの定義とheap変数は不要となったので、削除しました。

/****************************************************************************
 *	スタックレイアウト (2004/12/27追加)
 ****************************************************************************/

STACKINFO stack_info = {			// typedef struct _STACKINFO {
	{'S','T','A','C','K','I','N','F'},	//   char signature[8];	/* + 0,8: シグネチャ"STACKINF"固定 */
						//   /* システムスタック */
	0,					//   int* sysstack_top;	/* + 8,4: 先頭アドレス */
	0,					//   int* sysstack_end;	/* +12,4: 終端アドレス */
						//   /* ユーザースタック */
	0,					//   int* usrstack_top;	/* +16,4: 先頭アドレス */
	0,					//   int* usrstack_end;	/* +20,4: 終端アドレス */
						//   /* スタックポインタ */
	0,					//   int* ssp;		/* +24,4: システムスタックポインタ */
	0,					//   int* usp;		/* +28,4: ユーザースタックポインタ */
};						// } STACKINFO;		/* =32 */

static void
stack_layout()
{
#define CEIL4(i) (((int)(i) + 3) & ~3)

	STACKINFO* si = &stack_info;
	int sysstack_len;
	int usrstack_len;
	//
	int* pos;
	int* end;
	const char* err;

	/* リンク時に定義した、システムスタックサイズ、ユーザースタックサイズを取得します。
	 *	extern unsigned char_stacklen[];
	 *	extern unsigned char _usrstacklen[];
	 *	sysstack_len = (int)_stacklen;
	 *	usrstack_len = (int)_usrstacklen;
	 * ではダメです。
	 * コンパイラが間違った最適化を行って、常に非ゼロと見なされてしまう恐れがあります。
	 * 詳しくは、stack_addr()のコメント(現在は不使用。コメントアウト済み)を参照してください。
	 */
	asm volatile ("xld.w %0,    _stacklen" : "=r"(sysstack_len));
	asm volatile ("xld.w %0, _usrstacklen" : "=r"(usrstack_len));

	/* システムスタックは常にSRAMに配置します。
	 * _stacklenを0以外に値に定義して、システムスタックサイズを明示してください。
	 */
	sysstack_len = CEIL4(sysstack_len);
	if(!sysstack_len) {
		err = "ｼｽﾃﾑｽﾀｯｸｻｲｽﾞが\n指定されていません。";
		goto L_ERR;
	}
	si->sysstack_top = (int*)CEIL4(__END_DEFAULT_BSS);
	si->sysstack_end = (int*)((int)si->sysstack_top + sysstack_len);
	if(sysstack_len < 0x400) { /* システムスタックは少なくとも1KB必要、とします */
		err = "ｼｽﾃﾑｽﾀｯｸが\n小さすぎます。";
		goto L_ERR;
	}

	/* ユーザースタックはSRAMまたは高速RAMまたはSRAMに配置します。 */
	usrstack_len = CEIL4(usrstack_len);
	if(!usrstack_len) {
		/* _usrstacklenが0ならば、高速RAMの残りをユーザースタックとします。 */
		si->usrstack_top = (int*)(0x1000 + CEIL4(__SIZEOF_FRAM_ADDR));
		si->usrstack_end = (int*)0x2000;
		usrstack_len = (int)si->usrstack_end - (int)si->usrstack_top;
	} else {
		/* _usrstacklenが0以外ならば、SRAMにユーザースタックを確保します。 */
		si->usrstack_top = (int*)malloc(usrstack_len);
		if(!si->usrstack_top) {
			err = "ﾕｰｻﾞｰｽﾀｯｸが\n確保できません。";
			goto L_ERR;
		}
		si->usrstack_end = (int*)((int)si->usrstack_top + usrstack_len);
	}
	if(usrstack_len < 0x400) { /* ユーザースタックは少なくとも1KB必要、とします */
		err = "ﾕｰｻﾞｰｽﾀｯｸが\n小さすぎます。";
		goto L_ERR;
	}

	/* システムスタック溢れおよび使用量チェックのための乱数列を書き込んでおきます。 */
	pos = si->sysstack_top;
	asm volatile ("ld.w %0, %%sp" : "=r"(end)); /* システムスタックは既に使用中なので"end = si->sysstack_end"ではダメ!! */
	while(pos < end) {
		*pos = (int)pos;
		pos++;
	}

	/* ユーザースタック溢れおよび使用量チェックのための乱数列を書き込んでおきます。 */
	pos = si->usrstack_top;
	end = si->usrstack_end; /* ユーザースタックはまだ未使用なので全部埋めてしまって大丈夫です */
	while(pos < end) {
		*pos = (int)pos;
		pos++;
	}

	return;

L_ERR:
	memset(_def_vbuff, 0, sizeof _def_vbuff);
	pceLCDSetBuffer(_def_vbuff);
//不要	pceLCDDispStart();	//{{2014/12/31削除:cstart.cのpceAppInit00()でpceLCDDispStart()を呼び出し、液晶画面の表示を開始するようにしました。}}
	pceFontSetType(0);
	pceFontSetTxColor(3);
	pceFontSetBkColor(0);
	pceFontSetPos(0, 0); 
	pceFontPutStr(err);
	pceLCDTrans();
	while( (pcePadGetDirect() & PAD_SELECT)) { /** no job **/ }
	while(!(pcePadGetDirect() & PAD_SELECT)) { /** no job **/ }
	while( (pcePadGetDirect() & PAD_SELECT)) { /** no job **/ }
	asm("
		xld.w %r4, [0xC00000]	;// 強制リセット
		jp %r4
	");

#undef CEIL4
}

/*****************************************************************************
 *	スタックあふれ検査
 *****************************************************************************/

/* * Tue Apr 2 05:21:00 JST 2003 Naoyuki Sawa
 * - スタックあふれ検査の有効性について考察：
 *   せっかく実装したスタックあふれ検査ですが、本当に役に立つのでしょうか？
 *   スタックを高速RAMに置いた場合と、SRAMに置いた場合のそれぞれについて、検討してみます。
 * - スタックを高速RAMに置いた場合、スタックの底のすぐ下は転送した高速RAMコードか、
 *   または高速RAMコード転送を使っていなければFRAM4領域（0x0e00～0x1000）です。
 *   - 高速RAMコード転送を使っている場合、スタックがあふれてスタックチェックのマークを
 *   破壊したときには、たぶん高速RAMコードも破壊してしまっているでしょう。
 *   高速RAMコードは頻繁に使うものを転送しているはずなので、
 *   スタックチェックを行うまで高速RAMコードが呼ばれずにいる可能性は低く、
 *   すなわち高速RAMコードが呼ばれた途端に暴走してしまいます。
 *   チェックマークの位置をスタックの底ぎりぎりでなく少し余裕を持った位置に置けば
 *   ある程度は回避できますが、高速RAMはとても少ないので、スタックチェックだけのために
 *   余裕を見込んで容量を減らすのはちょっと厳しいです。
 *   - 高速RAMコード転送を使わず、FRAM4領域を破壊した場合は、ちょっと希望が持てます。
 *   FRAM4はpceLCDTrans()等のAPIが利用するだけなので、これらのAPIを呼び出す前に
 *   スタックが浅い位置まで復帰していれば、APIの動作に影響はありません。
 *   上手くいけば、スタックチェッカがスタック破壊を検出することができます。
 *   512バイトの余裕があるので、スタック破壊検出の可能性はまあまああります。
 * - スタックをSRAMに置いた場合、スタックの底の下はBSS領域です。
 *   BSSの配置は、cstart.o→ユーザープログラム→その他のライブラリ となっているので、
 *   通常、BSSの最後には_def_vbuff[]やmallocの内部ワーク、乱数シードなどが配置されています。
 *   スタックチェックマークを破壊したとき、勢い余って破壊した領域が、_def_vbuffやsrandなら、
 *   即座にプログラムの動作に異常をきたすことはないでしょう。
 *   スタックチェックが行われるまでプログラムが生きている可能性はあります。
 *   mallocの内部ワークを破壊した場合も、malloc関連のAPIが呼ばれなければ大丈夫です。
 *   以上より、SRAM上にスタックを取った場合は、スタックチェックは有効に働くと思われます。
 * - 実際のアプリケーション開発においても、SRAMにスタックを置いた状況で、
 *   スタックチェッカが有効に機能したケースが一度ありました。
 *   高速RAMにスタックを置いた状況でスタックチェッカが有効に機能したケースは、
 *   現在のところ、まだありません。
 */

#if 0 /*{{2004/12/27削除*/
///* スタックの底のアドレスを取得します。 
// * ・SRAM上にスタックを取った場合は、__END_DEFAULT_BSSがスタックの底となります。
// *   BSSセクションの直後にスタックが取られるからです。
// *   __END_DEFAULT_BSSはそのままで既に、ワード境界に整列されているはずです。
// * ・高速RAM上にスタックを取った場合は、0x1000がスタックの底となります。
// * ・スタックがどちらに取られているかは、シンボル_stacklenが0か0以外かで判別できます。
// *   0なら高速RAM、0以外ならSRAM上です。
// * ・2003/01/27現在、CLiPライブラリ使用アプリケーションは必ずSRAM上にスタックを取ります。
// *   従って判別は不要なのですが、今後のために、手順どおり判別しておくことにします。
// */
//static int*
//stack_addr()
//{
//	//extern unsigned char _stacklen[];
//	//extern unsigned char __END_DEFAULT_BSS[];
//	//return _stacklen ? (int*)__END_DEFAULT_BSS : (int*)0x1000;
//	//↑不可！
//	//  配列アドレスは、たとえ外部で0に定義されていたとしても、コンパイル時には非NULLと見なされてしまう。
//	//  このコードは_stacklenのゼロ検査が省かれ、return (int*)__END_DEFAULT_BSS; としてコンパイルされる。
//	//↓こうすればＯＫ！(2003/04/01)
//	pceAPPHEAD* pceAppHead = (pceAPPHEAD*)pceSystemGetInfo()->sram_top/*実際には0x100000固定*/;		/* pceAppHeadは、/usr/PIECE/lib/src/cstart.c内で定義されています。 */
//	return pceAppHead->stack_size ? (int*)(((int)pceAppHead->bss_end                         + 3) & ~3)	/* SRAM上にスタックを置いた場合は、BSS領域の直後がスタックの底となります。 */
//				      : (int*)(((int)__START_FRAM_ADDR + (int)__SIZEOF_FRAM_ADDR + 3) & ~3);	/* 高速RAMに高速コードとスタックを両方置いた場合は、高速コードの直後がスタックの底となります。 */
//}
#endif /*{{2004/12/27削除*/
// * Mon Dec 27 12:13:00 JST 2004 Naoyuki Sawa
// - スタックレイアウトの変更に伴い、{{2004/12/27削除～}}のブロックは削除しました。
//   スタックポインタを保持する変数は、system_sp→ssp、user_sp→uspに変更しました。
//   これ以降のコードでsystem_sp、user_spを使っていた部分は、すべてssp、uspに置換しました。

///* スタックの底にスタックあふれ検査のための定数を書き込んでおきます。 */
//static void
//stack_check_install()
//{
//	//*stack_addr() = STACKCHECK;
//	//↓2004/12/27変更
//	*stack_info.sysstack_top =
//	*stack_info.usrstack_top = STACKCHECK;
//}
//→2004/12/29 stack_layout()で行うようにしました。

/* スタックの底に書き込んだ定数が書き換えられていたら、スタックあふれです。 */
static void
stack_check()
{
	//if(*stack_addr() == STACKCHECK) return;
	//↓2004/12/27変更
	if((*stack_info.sysstack_top == (int)stack_info.sysstack_top) &&
	   (*stack_info.usrstack_top == (int)stack_info.usrstack_top)) return;

	/* ユーザーコンテキスト外では、DIE()は使えません。（yield()を呼んでいるから）
	 * ここで処理を回してしまうと、またスタック検査にひっかかかる恐れがあるので、
	 * ボタン監視ループで止めてしまうことにします。
	 */
	memset(_def_vbuff, 0, sizeof _def_vbuff);
	pceLCDSetBuffer(_def_vbuff);
//不要	pceLCDDispStart();	//{{2014/12/31削除:cstart.cのpceAppInit00()でpceLCDDispStart()を呼び出し、液晶画面の表示を開始するようにしました。}}
	pceFontSetType(0);
	pceFontSetTxColor(3);
	pceFontSetBkColor(0);
	pceFontSetPos(0, 0);
	pceFontPutStr("Stack Overflow!!");
	pceLCDTrans();
	while( (pcePadGetDirect() & PAD_SELECT)) { /** no job **/ }
	while(!(pcePadGetDirect() & PAD_SELECT)) { /** no job **/ }
	while( (pcePadGetDirect() & PAD_SELECT)) { /** no job **/ }
	asm("
		xld.w %r4, [0xC00000]	;// 強制リセット
		jp %r4
	");
}

/*****************************************************************************
 *	ヒープマネージャAPIの排他制御
 *****************************************************************************/

/* * 割り込みルーチン内でpceHeap*()APIが利用できるよう、pceHeap*()APIをフックし、排他制御することにしました。
 *
 * - 割り込みルーチン内でpceHeap*()APIを利用したいと考えた理由は、TCP/IPプロトコルドライバの送信ルーチンにて、
 *   IPデータグラムやUDPデータグラムを構築するための、一時的なバッファを確保するためです。
 *
 * - 以下のAPIは、カーネル内でpceHeapAlloc()APIを静的リンクしているため、排他制御の対象外となってしまいます。
 *	pceFileCreate()			⇒{{2014/09/05追記:「ファイル書き込み系関数の排他制御」を行いました。}}
 *	pceFileDelete()			⇒{{2014/09/05追記:「ファイル書き込み系関数の排他制御」を行いました。}}
 *	pceFileRename() //非公開API	⇒{{2014/09/05追記:アプリケーションから呼出せない関数なので、排他制御していません。}}
 *	pceFileLoad()			⇒{{2014/09/05追記:アプリケーションから利用しないと思うので、排他制御していません。}}
 *   以上のAPI使用時は、割り込みルーチン内でpceHeap*()APIを利用しないよう、適切に割り込みを停止してください。
 */

static void*   new_pceHeapAlloc          (unsigned long size            );
static int     new_pceHeapFree           (void* memp                    );
static int     new_pceHeapRealloc        (void* memp, unsigned long size);
static int     new_pceHeapGetMaxFreeSize (                              );

static void* (*old_pceHeapAlloc         )(unsigned long size            ) = new_pceHeapAlloc         ;
static int   (*old_pceHeapFree          )(void* memp                    ) = new_pceHeapFree          ;
static int   (*old_pceHeapRealloc       )(void* memp, unsigned long size) = new_pceHeapRealloc       ;
static int   (*old_pceHeapGetMaxFreeSize)(                              ) = new_pceHeapGetMaxFreeSize;

//{{2014/12/19変更:ヒープマネージャAPIの排他制御を、ENTER_CS/LEAVE_CS⇒ENTER_IL(1)/LEAVE_ILに変更しました。
//static void*   new_pceHeapAlloc          (unsigned long size            ) { void* result; ENTER_CS;
//      result = old_pceHeapAlloc          (              size            );                LEAVE_CS; return result; }
//static int     new_pceHeapFree           (void* memp                    ) { int   result; ENTER_CS;
//      result = old_pceHeapFree           (      memp                    );                LEAVE_CS; return result; }
//static int     new_pceHeapRealloc        (void* memp, unsigned long size) { int   result; ENTER_CS;
//      result = old_pceHeapRealloc        (      memp,               size);                LEAVE_CS; return result; }
//static int     new_pceHeapGetMaxFreeSize (                              ) { int   result; ENTER_CS;
//      result = old_pceHeapGetMaxFreeSize (                              );                LEAVE_CS; return result; }
//↓2014/12/19変更:ヒープマネージャAPIの排他制御を、ENTER_CS/LEAVE_CS⇒ENTER_IL(1)/LEAVE_ILに変更しました。
static void*   new_pceHeapAlloc          (unsigned long size            ) { void* result; ENTER_IL(1);
      result = old_pceHeapAlloc          (              size            );                LEAVE_IL; return result; }
static int     new_pceHeapFree           (void* memp                    ) { int   result; ENTER_IL(1);
      result = old_pceHeapFree           (      memp                    );                LEAVE_IL; return result; }
static int     new_pceHeapRealloc        (void* memp, unsigned long size) { int   result; ENTER_IL(1);
      result = old_pceHeapRealloc        (      memp,               size);                LEAVE_IL; return result; }
static int     new_pceHeapGetMaxFreeSize (                              ) { int   result; ENTER_IL(1);
      result = old_pceHeapGetMaxFreeSize (                              );                LEAVE_IL; return result; }
//}}2014/12/19変更:ヒープマネージャAPIの排他制御を、ENTER_CS/LEAVE_CS⇒ENTER_IL(1)/LEAVE_ILに変更しました。

/* * 1回呼ぶと、pceHeap*()APIをフックします。(pceAppInit()から利用)
 *   2回呼ぶと、pceHeap*()APIを元に戻します。(pceAppExit()から利用)
 *
 * - clipgcモジュールの利用など、ユーザープログラムがpceHeap*()APIをHook/Unhookすることも有り得ます。
 *   hook_unhook_heapman()によるHook/Unhookと、ユーザープログラムによるHook/Unhookが交差しないよう、
 *   1回めのhook_unhook_heapman()呼び出しは確実にアプリケーション起動の直後(pceAppInit()の先頭)に、
 *   2回めのhook_unhook_heapman()呼び出しは確実にアプリケーション終了の直前(pceAppExit()の終端)に行ってください。
 */
static void
hook_unhook_heapman()
{
	old_pceHeapAlloc          = (PCEKSENT)pceVectorSetKs(KSNO_HeapAlloc,          old_pceHeapAlloc         );
	old_pceHeapFree           = (PCEKSENT)pceVectorSetKs(KSNO_HeapFree,           old_pceHeapFree          );
	old_pceHeapRealloc        = (PCEKSENT)pceVectorSetKs(KSNO_HeapRealloc,        old_pceHeapRealloc       );
	old_pceHeapGetMaxFreeSize = (PCEKSENT)pceVectorSetKs(KSNO_HeapGetMaxFreeSize, old_pceHeapGetMaxFreeSize);
}

/*****************************************************************************
 *	ファイル書き込み系関数の排他制御
 *****************************************************************************/

/* * Fri Sep 05 21:36:08 JST 2014 Naoyuki Sawa
 * - ファイル書き込み系関数の排他制御を追加しました。
 * - 割り込みルーチン内でpceFile*()APIが利用できるよう、pceFile*()APIをフックし、排他制御することにしました。
 *   排他制御の方法は、「ヒープマネージャAPIの排他制御」と同様です。
 *   「ヒープマネージャAPIの排他制御」のコメントを参照してください。
 * - pceFileCreate(),pceFileDelete(),pceFileWriteSct()が、割り込みルーチン内で利用できるようになりました。
 *   ただし、ディレクトリエントリの排他制御は行っていないことに注意してください。
 *   ・例1: 割り込み外の処理でファイルを開いて、処理を行っている間に、割り込み内の処理でファイルを削除すると、予測できない結果になります。
 *   ・例2: 割り込み外の処理でpceFileFindNext()を使っている間に、割り込み内の処理でファイルを追加,削除すると、予測できない結果になります。
 *   排他制御を行ったとはいえ、割り込み内と割り込み外の両方で、ファイル書き込み系関数を使うことは、避ける方が安全です。
 * - 排他制御に加えて、一時的に24MHzモードに戻す処理も追加しました。
 *   ファイル書き込み系関数は、48Mhzモードでは動作しないからです。(詳細は、「24MHz/48Mhzモード切り替え」のコメントを参照してください。)
 *   今後は、アプリケーションが明示的に24MHzモードに戻すこと無く、ファイル書き込み系関数を呼び出すことができます。
 * - pceFlashErase(),pceFlashWrite()は排他制御していません。
 *   pceFlashErase(),pceFlashWrite()をアプリケーションから直接使用することは無いと思いますが、
 *   もし使用する場合は、これまでどおり、明示的にturbo(0)を読んで24MHzモードに戻してください。
 * - まだ、検証を充分に行っていません。											┓
 *   排他制御の処理は問題ないと思いますが、割り込み内でフラッシュ書き換えのような遅い処理を実行して問題ないか心配です。	┣※TODO:検証せよ
 *   カーネルのフラッシュ書き換え処理が、pceCPUSetSpeed()を呼び出しており、USB割り込みやUSBクロックへの影響も心配です。	┛
 * - ■■■■■重用■■■■■															//┐
 *   実際に割り込み内でpceFileCreate,pceFileDelete,pceFileWriteSctを呼び出す場合は、FRAM4領域の退避復元処理が必要です。				//│
 *   具体的には、clipptp.hのST_PTP.save_FRAM4のコメントを参照してください。									//│
 * - ↓↓↓↓↓TODO↓↓↓↓↓															//│ * Sat Sep 20 08:44:17 JST 2014 Naoyuki Sawa 追記
 *   現在の実装では、STREAMINFO.fram4_saveとST_PTP.save_FRAM4に、個別にFRAM4領域退避用メモリが有り、無駄です。					//│ - 左記のTODOを実装しました。
 *   これらをまとめて、FRAM4領域退避用メモリを1個にし、全体から共用できるはずです。								//│   割り込み内でpceFileCreate,pceFileDelete,pceFileWriteSctを使う場合にも、
 *   さらに、まとめたFRAM4領域退避用メモリをを使い、new_pceFileCreate,new_pceFileDelete,new_pceFileWriteSctで退避と復元するようにすれば、	//│   呼び出し元で、明示的にFRAM4の退避復元の処理を行う必要は無くなりました。
 *   stream_fill()や、clipptp.cの各関数での個別の退避復元処理を、無くせるはずです。								//│
 *   もし今後新たに、割り込み内でpceFileCreate,pceFileDelete,pceFileWriteSctを使う場合にも、FRAM4の退避復元を気にする必要が無くなります。	//│
 *   大きな利点があるので、ぜひ、そうするべきです。												//┘
 */

static int   new_pceFileCreate   (const char* fname, unsigned long size          );
static int   new_pceFileDelete   (const char* fname                              );
static int   new_pceFileWriteSct (FILEACC* pfa, const void* ptr, int sct, int len);

static int (*old_pceFileCreate  )(const char* fname, unsigned long size          ) = new_pceFileCreate  ;
static int (*old_pceFileDelete  )(const char* fname                              ) = new_pceFileDelete  ;
static int (*old_pceFileWriteSct)(FILEACC* pfa, const void* ptr, int sct, int len) = new_pceFileWriteSct;

//{{2014/09/20変更:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
//static int   new_pceFileCreate   (const char* fname, unsigned long size          ) { int result, mode = turbo(0); ENTER_CS;
//    result = old_pceFileCreate   (            fname,               size          ); LEAVE_CS; turbo(mode); return result; }
//static int   new_pceFileDelete   (const char* fname                              ) { int result, mode = turbo(0); ENTER_CS;
//    result = old_pceFileDelete   (            fname                              ); LEAVE_CS; turbo(mode); return result; }
//static int   new_pceFileWriteSct (FILEACC* pfa, const void* ptr, int sct, int len) { int result, mode = turbo(0); ENTER_CS;
//    result = old_pceFileWriteSct (         pfa,             ptr,     sct,     len); LEAVE_CS; turbo(mode); return result; }
//↓2014/09/20変更:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
//static int   new_pceFileCreate   (const char* fname, unsigned long size          ) { int result, mode; mode = turbo(0); ENTER_CS; save_FRAM4(1);	//┐ENTER_CSとsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
//    result = old_pceFileCreate   (            fname,               size          );  save_FRAM4(0); LEAVE_CS; turbo(mode); return result; }		//┘LEAVE_CSとsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
//static int   new_pceFileDelete   (const char* fname                              ) { int result, mode; mode = turbo(0); ENTER_CS; save_FRAM4(1);	//┐ENTER_CSとsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
//    result = old_pceFileDelete   (            fname                              );  save_FRAM4(0); LEAVE_CS; turbo(mode); return result; }		//┘LEAVE_CSとsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
//static int   new_pceFileWriteSct (FILEACC* pfa, const void* ptr, int sct, int len) { int result, mode; mode = turbo(0); ENTER_CS; save_FRAM4(1);	//┐ENTER_CSとsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
//    result = old_pceFileWriteSct (         pfa,             ptr,     sct,     len);  save_FRAM4(0); LEAVE_CS; turbo(mode); return result; }		//┘LEAVE_CSとsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
//↓2014/12/19変更:ファイル書き込み系関数の排他制御を、ENTER_CS/LEAVE_CS⇒ENTER_IL(1)/LEAVE_ILに変更しました。
static int   new_pceFileCreate   (const char* fname, unsigned long size          ) { int result, mode; mode = turbo(0); ENTER_IL(1); save_FRAM4(1);	//┐ENTER_IL(1)とsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
    result = old_pceFileCreate   (            fname,               size          );  save_FRAM4(0); LEAVE_IL; turbo(mode); return result; }		//┘LEAVE_ILとsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
static int   new_pceFileDelete   (const char* fname                              ) { int result, mode; mode = turbo(0); ENTER_IL(1); save_FRAM4(1);	//┐ENTER_IL(1)とsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
    result = old_pceFileDelete   (            fname                              );  save_FRAM4(0); LEAVE_IL; turbo(mode); return result; }		//┘LEAVE_ILとsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
static int   new_pceFileWriteSct (FILEACC* pfa, const void* ptr, int sct, int len) { int result, mode; mode = turbo(0); ENTER_IL(1); save_FRAM4(1);	//┐ENTER_IL(1)とsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
    result = old_pceFileWriteSct (         pfa,             ptr,     sct,     len);  save_FRAM4(0); LEAVE_IL; turbo(mode); return result; }		//┘LEAVE_ILとsave_FRAM4()の順番に注意。割込み許可状態でsave_FRAM4()を呼び出してはいけないから。
//}}2014/12/19変更:ファイル書き込み系関数の排他制御を、ENTER_CS/LEAVE_CS⇒ENTER_IL(1)/LEAVE_ILに変更しました。

static void hook_unhook_file() {
	old_pceFileCreate   = (PCEKSENT)pceVectorSetKs(KSNO_FileCreate  , old_pceFileCreate  );
	old_pceFileDelete   = (PCEKSENT)pceVectorSetKs(KSNO_FileDelete  , old_pceFileDelete  );
	old_pceFileWriteSct = (PCEKSENT)pceVectorSetKs(KSNO_FileWriteSct, old_pceFileWriteSct);
}

/*****************************************************************************
 *	FRAM4領域の退避復元
 *****************************************************************************/

void save_FRAM4(int flg/*1=退避,0=復元*/) {
	static void*         m_buf;	//FRAM4領域退避用メモリ
	static unsigned char m_flg;	//FRAM4領域退避中フラグ
	/* 割込み許可状態で、当関数を呼び出してはいけない。 */
    //	if(get_psr().ie) { DIE(); }						//この行の判定方法は間違い。stream_fill()から呼び出された場合は、ie=1,il=6であるから。
	{ const struct psr psr = get_psr(); if(psr.ie && !psr.il) { DIE(); } }	//この行の判定方法が正しい。ie又はilのどちらかで割込み禁止されていることをを確認する。stream_fill()から呼び出された場合、ie=0,il>0であり、ミキシング割込み中の多重割込みによる再度のミキシング割込みや赤外線割込みは発生するが、それらの割込みの中ではアプリケーション処理は呼び出されず、当関数が呼び出されることも無いので安全である。
	/* 当関数が最初に呼び出された時に、FRAM4領域退避用メモリを確保する。 */
	if(!m_buf) {
		m_buf = malloc(FRAM4_SIZE);
		if(!m_buf) { DIE(); }	//メモリ不足
	}
	/* FRAM4領域の退避～復元を、ネストしてはいけない。 */
	if(m_flg == flg) { DIE(); }	//退避中の退避や、復元中の復元でないことを確認する。
	   m_flg  = flg;		//フラグを、退避⇒復元,又は,復元⇒退避に切り替える。
	/* FRAM4領域を退避,又は,復元する。 */
	if(flg) {
		memcpy(m_buf, FRAM4_START, FRAM4_SIZE);	//FRAM4領域退避
	} else {
		memcpy(FRAM4_START, m_buf, FRAM4_SIZE);	//FRAM4領域復元
	}
}

/****************************************************************************
 *	USB割込みとタイマ割込みの割込み優先度を下げる
 ****************************************************************************/

/* USB割込みとタイマ割込みの割込み優先度を下げます。
 * [note]
 * 	- 一回呼ぶと優先度を下げ、もう一度呼ぶと元に戻します。
 *	- P/ECE Kernelでは、これらの割込み優先度は(6)に設定されています。
 *	  割込み優先度(6)のままでは、ミキシング割込みの多重割込み受付中の割込み優先度(5)よりも高く、
 *	  stream_fill()の処理に割込んでしまう問題があります。問題が生じるケースの具体例としては:
 *	  ・stream_fill()から呼び出されたストリームコールバック処理中にタイマ割込みが呼び出される問題がある。
 *	    タイマ割込みの中でSndSeq_Exec()を呼び出している場合、SndSeq_Exec()の中からサウンドのデバイス関数(TrueSpeech_PlayCh()等)が呼び出される。
 *	    ストリームコールバック処理とデバイス関数からのメモリ書き換えが衝突し、サウンドのデバイス構造体(ST_TrueSpeech等)が破壊することがあった。
 *	  ・stream_fill()から呼び出されたストリームコールバック処理中にUSB割込み処理が生じる問題がある。
 *	    ストリームコールバック処理がFRAM4スタック切り替えを使用していた場合、スタックは小さく、USB割込み処理によってスタックがあふれることがあった。
 *	  上記の問題を避けるため、USB割込みとタイマ割込みの割込み優先度を、最低(1)に下げることにしました。
 *	  元々、これらの割込み処理に即時性は必要無いので、最低優先度(1)で十分です。
 */
static void lower_restore_interrupt_priority() {
	static uint8_t save_bINT_PP01L_PP1L   = 1; /* Port input 1 interrupt priority */
	static uint8_t save_bINT_P16T01_P16T0 = 1; /* 16-bit timer 0 interrupt priority */
	int tmp;
	//\usr\PIECE\sysdev\pcekn\timer.cのInitTimer()にて、
	//＞// USBインターフェース用の INT_N 割り込み (K51)
	//＞bp[0x260] = 0x60; // ポート入力割り込み1 プライオリティ (FTP1=6)
	//と設定されている割り込みプライオリティを、1に置き換える,又は,元に戻す。
	tmp = save_bINT_PP01L_PP1L;
	      save_bINT_PP01L_PP1L = bINT_PP01L_PP1L;
	                             bINT_PP01L_PP1L = tmp;
	//\usr\PIECE\sysdev\pcekn\timer.cのInitTimer()にて、
	//＞// タイマ割り込み
	//＞bp[0x266] = 6; // 16bitタイマ0 割り込みプライオリティ
	//と設定されている割り込みプライオリティを、1に置き換える,又は,元に戻す。
	tmp = save_bINT_P16T01_P16T0;
	      save_bINT_P16T01_P16T0 = bINT_P16T01_P16T0;
	                               bINT_P16T01_P16T0 = tmp;
}

/*****************************************************************************
 *	4x6ドットフォントの半角カナ拡張
 *****************************************************************************/
#if 0	//{{2015/05/26変更:4x5ピクセルフォントを追加しました。
//===========================================================================
/* * Sun Feb 01 03:00:42 JST 2015 Naoyuki Sawa
 * - 「4x6ドットフォントの半角カナ拡張」を省メモリ化しました。
 *   USE_VOLATILE_FONTを定義すると、省メモリ化が有効になります。
 *   USE_VOLATILE_FONTを定義しなければ、これまでと同じになります。
 *   省メモリ化を有効にした場合のメモリ節約量は、632バイト節約です。
 * - 省メモリ化した方法は、以下のとおりです。
 *   これまでは、KanaFive_20_7F[]とKanaFive_A0_DF[]をP/ECEの極小フォントと同じ形式で格納していました。
 *   new_pceFontGetAdrs()から直接返せる利点は有りますが、P/ECEの極小フォント形式は無駄が多くメモリの無駄が生じていました。
 *   「KanaFive(カナファイブ)」は全ての文字が3×5ドット以内なので15ビットで足りるにもかかわらず、
 *   P/ECEの極小フォント形式はパディングを含めて8×6ドット(48ビット)だから三倍の無駄が生じていました。
 * - 省メモリ化を有効にすると、一文字当り16ビット(3×5ドット+余り1ビット)で定義するようにします。
 *   new_pceFontGetAdrs()が16ビットのフォントデータをP/ECEの極小フォント形式に展開して、展開バッファのアドレスを返します。
 *   従って、省メモリ化を有効にするとpceFontGetAdrs()が返すフォントのアドレスは揮発性になります。				←←←←←←←←←←重要
 *   pceFontGetAdrs()が返したフォントのアドレス(が指すデータ)は、次回のpceFontGetAdrs()で破壊されるようになります。
 *   厳密には、これはpceFontGetAdrs()の仕様と異なるのですが、実用上は問題ないと思います。
 *   普通のプログラムでは、pceFontGetAdrs()が返したアドレスを保持したまま再度pceFontGetAdrs()を呼び出すような使い方はしないからです。
 *   pceFontGetAdrs()を割り込み外と割り込み内で同時に使用するような事もしないと思います。
 *   もしそのような使用方法を行ったとしても、フォント描画が崩れるだけで、プログラムが暴走するような事はありません。
 * - 以上の理由より、実用上問題無いと判断し、上述の方法で省メモリ化を行うことにしました。
 *   もし問題が生じた場合は、USE_VOLATILE_FONTの定義をコメントアウトして、省メモリ化を無効にしてください。
 */
#define USE_VOLATILE_FONT
#ifdef  USE_VOLATILE_FONT
#define DEFINE_FONT(a,b,c,d,e) (a<<0|b<<3|c<<6|d<<9|e<<12  )
#else //USE_VOLATILE_FONT
#define DEFINE_FONT(a,b,c,d,e) {a<<5,b<<5,c<<5,d<<5,e<< 5,0}
#endif//USE_VOLATILE_FONT
/*--------------------------------------------------------------------------*/
/* * Sun Apr 17 02:15:29 JST 2011 Naoyuki Sawa
 * - 「WentWayUp: WebLog (http://wentwayup.tamaliver.jp/)」さんの、5×5ドットフォント「KanaFive(カナファイブ)」を使わせて頂きました。
 *   「5×5ドットフォント完成版 (http://wentwayup.tamaliver.jp/e93615.html)」のWindowsフォントは、Windows 7では表示できなかったため、
 *   「5×5フォント改 (http://wentwayup.tamaliver.jp/e59624.html)」の画像から、P/ECEの4x6ドットフォント互換データ形式に変換しました。
 * -  半角カナだけでなく、数字・アルファベット・記号も、KanaFiveを使うようにしました。
 *    数字・アルファベット・記号は、P/ECEシステムフォントのままにする方が良い場合は、USE_KANAFIVE_ASCIIをコメントアウトしてください。
 */
#define USE_KANAFIVE_ASCII
#ifdef  USE_KANAFIVE_ASCII
#ifdef  USE_VOLATILE_FONT
static const unsigned short KanaFive_20_7F[]    = {
#else //USE_VOLATILE_FONT
static const unsigned char  KanaFive_20_7F[][6] = {
#endif//USE_VOLATILE_FONT
	DEFINE_FONT(0,0,0,0,0),/* 20   */
	DEFINE_FONT(2,2,2,0,2),/* 21 ! */
	DEFINE_FONT(5,5,0,0,0),/* 22 " */
	DEFINE_FONT(5,7,5,7,5),/* 23 # */
	DEFINE_FONT(3,6,7,3,6),/* 24 $ */
	DEFINE_FONT(4,1,2,4,1),/* 25 % */
	DEFINE_FONT(2,4,2,5,7),/* 26 & */
	DEFINE_FONT(2,4,0,0,0),/* 27 ' */
	DEFINE_FONT(1,2,2,2,1),/* 28 ( */
	DEFINE_FONT(4,2,2,2,4),/* 29 ) */
	DEFINE_FONT(0,5,2,5,0),/* 2A * */
	DEFINE_FONT(0,2,7,2,0),/* 2B + */
	DEFINE_FONT(0,0,0,2,4),/* 2C , */
	DEFINE_FONT(0,0,7,0,0),/* 2D - */
	DEFINE_FONT(0,0,0,0,2),/* 2E . */
	DEFINE_FONT(1,1,2,4,4),/* 2F / */
	DEFINE_FONT(7,5,5,5,7),/* 30 0 */
	DEFINE_FONT(2,6,2,2,7),/* 31 1 */
	DEFINE_FONT(6,1,2,4,7),/* 32 2 */
	DEFINE_FONT(6,1,6,1,6),/* 33 3 */
	DEFINE_FONT(1,3,5,7,1),/* 34 4 */
	DEFINE_FONT(7,4,6,1,6),/* 35 5 */
	DEFINE_FONT(3,4,7,5,7),/* 36 6 */
	DEFINE_FONT(7,1,2,2,2),/* 37 7 */
	DEFINE_FONT(7,5,2,5,7),/* 38 8 */
	DEFINE_FONT(7,5,7,1,6),/* 39 9 */
	DEFINE_FONT(0,2,0,2,0),/* 3A : */
	DEFINE_FONT(0,2,0,2,4),/* 3B ; */
	DEFINE_FONT(1,2,4,2,1),/* 3C < */
	DEFINE_FONT(0,7,0,7,0),/* 3D = */
	DEFINE_FONT(4,2,1,2,4),/* 3E > */
	DEFINE_FONT(6,1,2,0,2),/* 3F ? */
	DEFINE_FONT(6,1,3,5,7),/* 40 @ */
	DEFINE_FONT(2,5,7,5,5),/* 41 A */
	DEFINE_FONT(6,5,6,5,6),/* 42 B */
	DEFINE_FONT(3,4,4,4,3),/* 43 C */
	DEFINE_FONT(6,5,5,5,6),/* 44 D */
	DEFINE_FONT(7,4,7,4,7),/* 45 E */
	DEFINE_FONT(7,4,7,4,4),/* 46 F */
	DEFINE_FONT(3,4,5,5,3),/* 47 G */
	DEFINE_FONT(5,5,7,5,5),/* 48 H */
	DEFINE_FONT(7,2,2,2,7),/* 49 I */
	DEFINE_FONT(1,1,5,5,6),/* 4A J */
	DEFINE_FONT(5,5,6,5,5),/* 4B K */
	DEFINE_FONT(4,4,4,4,7),/* 4C L */
	DEFINE_FONT(5,7,5,5,5),/* 4D M */
	DEFINE_FONT(6,5,5,5,5),/* 4E N */
	DEFINE_FONT(2,5,5,5,2),/* 4F O */
	DEFINE_FONT(7,5,6,4,4),/* 50 P */
	DEFINE_FONT(2,5,5,2,3),/* 51 Q */
	DEFINE_FONT(6,5,6,5,5),/* 52 R */
	DEFINE_FONT(3,4,2,1,6),/* 53 S */
	DEFINE_FONT(7,2,2,2,2),/* 54 T */
	DEFINE_FONT(5,5,5,5,7),/* 55 U */
	DEFINE_FONT(5,5,5,5,2),/* 56 V */
	DEFINE_FONT(5,5,5,7,5),/* 57 W */
	DEFINE_FONT(5,5,2,5,5),/* 58 X */
	DEFINE_FONT(5,5,2,2,2),/* 59 Y */
	DEFINE_FONT(7,1,2,4,7),/* 5A Z */
	DEFINE_FONT(3,2,2,2,3),/* 5B [ */
	DEFINE_FONT(4,4,2,1,1),/* 5C \ */
	DEFINE_FONT(6,2,2,2,6),/* 5D ] */
	DEFINE_FONT(2,5,0,0,0),/* 5E ^ */
	DEFINE_FONT(0,0,0,0,7),/* 5F _ */
	DEFINE_FONT(2,1,0,0,0),/* 60 ` */
	DEFINE_FONT(0,6,3,5,3),/* 61 a */
	DEFINE_FONT(4,6,5,5,6),/* 62 b */
	DEFINE_FONT(0,3,4,4,3),/* 63 c */
	DEFINE_FONT(1,3,5,5,3),/* 64 d */
	DEFINE_FONT(0,3,5,6,3),/* 65 e */
	DEFINE_FONT(1,2,7,2,2),/* 66 f */
	DEFINE_FONT(0,3,5,3,6),/* 67 g */
	DEFINE_FONT(4,6,5,5,5),/* 68 h */
	DEFINE_FONT(2,0,2,2,2),/* 69 i */
	DEFINE_FONT(2,0,2,2,4),/* 6A j */
	DEFINE_FONT(4,5,6,5,5),/* 6B k */
	DEFINE_FONT(6,2,2,2,2),/* 6C l */
	DEFINE_FONT(0,5,7,5,5),/* 6D m */
	DEFINE_FONT(0,6,5,5,5),/* 6E n */
	DEFINE_FONT(0,2,5,5,2),/* 6F o */
	DEFINE_FONT(0,7,5,6,4),/* 70 p */
	DEFINE_FONT(0,7,5,3,1),/* 71 q */
	DEFINE_FONT(0,3,4,4,4),/* 72 r */
	DEFINE_FONT(0,3,6,1,6),/* 73 s */
	DEFINE_FONT(2,7,2,2,3),/* 74 t */
	DEFINE_FONT(0,5,5,5,3),/* 75 u */
	DEFINE_FONT(0,5,5,5,2),/* 76 v */
	DEFINE_FONT(0,5,5,7,5),/* 77 w */
	DEFINE_FONT(0,5,2,5,5),/* 78 x */
	DEFINE_FONT(0,5,5,2,4),/* 79 y */
	DEFINE_FONT(0,7,2,4,7),/* 7A z */
	DEFINE_FONT(3,2,4,2,3),/* 7B { */
	DEFINE_FONT(2,2,2,2,2),/* 7C | */
	DEFINE_FONT(6,2,1,2,6),/* 7D } */
	DEFINE_FONT(3,6,0,0,0),/* 7E ~ */
	DEFINE_FONT(0,0,0,0,0),/* 7F   */
};
#endif/*USE_KANAFIVE_ASCII*/
#ifdef  USE_VOLATILE_FONT
static const unsigned short KanaFive_A0_DF[]    = {
#else //USE_VOLATILE_FONT
static const unsigned char  KanaFive_A0_DF[][6] = {
#endif//USE_VOLATILE_FONT
	DEFINE_FONT(0,0,0,0,0),/* A0   */
	DEFINE_FONT(0,0,0,6,6),/* A1 ｡ */
	DEFINE_FONT(3,2,2,2,0),/* A2 ｢ */
	DEFINE_FONT(0,2,2,2,6),/* A3 ｣ */
	DEFINE_FONT(0,0,0,4,2),/* A4 ､ */
	DEFINE_FONT(0,0,2,0,0),/* A5 ･ */
	DEFINE_FONT(7,1,7,1,2),/* A6 ｦ */
	DEFINE_FONT(0,7,1,2,6),/* A7 ｧ */
	DEFINE_FONT(0,1,6,2,2),/* A8 ｨ */
	DEFINE_FONT(0,2,7,5,1),/* A9 ｩ */
	DEFINE_FONT(0,7,2,2,7),/* AA ｪ */
	DEFINE_FONT(0,1,7,3,5),/* AB ｫ */
	DEFINE_FONT(0,4,7,5,4),/* AC ｬ */
	DEFINE_FONT(0,6,2,2,7),/* AD ｭ */
	DEFINE_FONT(0,7,3,1,7),/* AE ｮ */
	DEFINE_FONT(0,6,7,1,2),/* AF ｯ */
	DEFINE_FONT(0,0,7,0,0),/* B0 ｰ */
	DEFINE_FONT(7,1,2,2,4),/* B1 ｱ */
	DEFINE_FONT(1,6,2,2,2),/* B2 ｲ */
	DEFINE_FONT(2,7,5,1,2),/* B3 ｳ */
	DEFINE_FONT(7,2,2,2,7),/* B4 ｴ */
	DEFINE_FONT(1,7,1,3,5),/* B5 ｵ */
	DEFINE_FONT(2,7,3,5,5),/* B6 ｶ */
	DEFINE_FONT(2,7,2,7,2),/* B7 ｷ */
	DEFINE_FONT(3,5,1,1,2),/* B8 ｸ */
	DEFINE_FONT(4,7,2,2,4),/* B9 ｹ */
	DEFINE_FONT(7,1,1,1,7),/* BA ｺ */
	DEFINE_FONT(5,7,5,1,2),/* BB ｻ */
	DEFINE_FONT(4,0,5,1,6),/* BC ｼ */
	DEFINE_FONT(7,1,2,5,5),/* BD ｽ */
	DEFINE_FONT(4,7,5,4,3),/* BE ｾ */
	DEFINE_FONT(1,5,1,2,4),/* BF ｿ */
	DEFINE_FONT(3,5,3,1,2),/* C0 ﾀ */
	DEFINE_FONT(7,2,7,2,4),/* C1 ﾁ */
	DEFINE_FONT(6,7,1,1,2),/* C2 ﾂ */
	DEFINE_FONT(7,0,7,2,4),/* C3 ﾃ */
	DEFINE_FONT(4,4,7,4,4),/* C4 ﾄ */
	DEFINE_FONT(2,7,2,2,4),/* C5 ﾅ */
	DEFINE_FONT(7,0,0,0,7),/* C6 ﾆ */
	DEFINE_FONT(7,1,5,2,5),/* C7 ﾇ */
	DEFINE_FONT(2,7,1,2,7),/* C8 ﾈ */
	DEFINE_FONT(1,1,1,2,4),/* C9 ﾉ */
	DEFINE_FONT(2,1,5,5,5),/* CA ﾊ */
	DEFINE_FONT(4,7,4,4,3),/* CB ﾋ */
	DEFINE_FONT(7,1,1,1,2),/* CC ﾌ */
	DEFINE_FONT(2,5,1,1,0),/* CD ﾍ */
	DEFINE_FONT(2,7,2,7,7),/* CE ﾎ */
	DEFINE_FONT(7,1,5,2,1),/* CF ﾏ */
	DEFINE_FONT(7,0,3,0,7),/* D0 ﾐ */
	DEFINE_FONT(2,4,4,5,7),/* D1 ﾑ */
	DEFINE_FONT(1,1,5,2,5),/* D2 ﾒ */
	DEFINE_FONT(7,2,7,2,3),/* D3 ﾓ */
	DEFINE_FONT(4,7,5,4,4),/* D4 ﾔ */
	DEFINE_FONT(6,2,2,2,7),/* D5 ﾕ */
	DEFINE_FONT(7,1,7,1,7),/* D6 ﾖ */
	DEFINE_FONT(7,0,7,1,2),/* D7 ﾗ */
	DEFINE_FONT(5,5,5,1,2),/* D8 ﾘ */
	DEFINE_FONT(2,6,6,6,7),/* D9 ﾙ */
	DEFINE_FONT(4,4,4,5,6),/* DA ﾚ */
	DEFINE_FONT(7,5,5,5,7),/* DB ﾛ */
	DEFINE_FONT(7,5,1,1,2),/* DC ﾜ */
	DEFINE_FONT(6,0,0,1,6),/* DD ﾝ */
	DEFINE_FONT(4,2,0,0,0),/* DE ﾞ */
	DEFINE_FONT(6,6,0,0,0),/* DF ﾟ */
};
/*--------------------------------------------------------------------------*/
/*static*/ const unsigned char* new_pceFontGetAdrs(unsigned short code);
/*static*/ void new_pceFontSetType(int type);
static const unsigned char* (*old_pceFontGetAdrs)(unsigned short code) = new_pceFontGetAdrs;
static void                 (*old_pceFontSetType)(int type           ) = new_pceFontSetType;
static unsigned char fnt_typ __attribute__((unused))/*asmブロックから参照*/;	/* \usr\PIECE\sysdev\pcekn\work.h の fnt_typ と同じ値を保持 */
/*--------------------------------------------------------------------------*/
static void hook_unhook_font() {
	old_pceFontGetAdrs = (PCEKSENT)pceVectorSetKs(KSNO_FontGetAdrs, old_pceFontGetAdrs);
	old_pceFontSetType = (PCEKSENT)pceVectorSetKs(KSNO_FontSetType, old_pceFontSetType);
}
/*--------------------------------------------------------------------------*/
#ifdef  USE_VOLATILE_FONT
static unsigned char fnt_buf[6] __attribute__((unused))/*asmブロックから参照*/;	/* フォント展開バッファ */
#ifndef PIECE
/*static*/ const unsigned char* new_pceFontGetAdrs(unsigned short code) {
	if((fnt_typ & ~0x80) == 2) {	/* フォントの種類のみ比較する */
		int i, x;
#ifdef  USE_KANAFIVE_ASCII
		if((code >= 0x20) && (code <= 0x7F)) {
			x = KanaFive_20_7F[code - 0x20];
			goto L_EXPAND;
		}
#endif/*USE_KANAFIVE_ASCII*/
		if((code >= 0xA0) && (code <= 0xDF)) {
			x = KanaFive_A0_DF[code - 0xA0];
	L_EXPAND:	x <<= 5;
			for(i = 0; i < 5; i++) {
				fnt_buf[i] = x;
				x = (x >> 3) & ~0x1F;
			}
			return fnt_buf + 0x80000000;
		}
	}
	return (*old_pceFontGetAdrs)(code);
}
#else /*PIECE*/
asm("
		.code
		.align		1
new_pceFontGetAdrs:
		xld.ub		%r10, [fnt_typ]			;// %r10 := fnt_typ
		xand		%r10, %r10, 0x7F		;// %r10 := fnt_typ & ~0x80
		cmp		%r10, 2				;// if((fnt_typ & ~0x80) != 2) {
		jrne		new_pceFontGetAdrs_TAIL		;//   
		"
#ifdef  USE_KANAFIVE_ASCII
		"
		xsub		%r10, %r12, 0x20		;//   %r10 := code - 0x20
		xcmp		%r10, 0x5F			;//   if((code - 0x20) <= (0x7F - 0x20)) {
		jrugt		new_pceFontGetAdrs_KANA		;//     
		sla		%r10, 1				;//     %r10 :=                   (code - 0x20) * sizeof(unsigned short)
		ext		KanaFive_20_7F@ah		;//     %r11 := x = KanaFive_20_7F[code - 0x20]
		ext		KanaFive_20_7F@al		;//     
		ld.uh		%r11, [%r10]			;//     
		jp		new_pceFontGetAdrs_EXPAND	;//     goto EXPAND }
		"
#endif/*USE_KANAFIVE_ASCII*/
		"
new_pceFontGetAdrs_KANA:
		xsub		%r10, %r12, 0xA0		;//   %r10 := code - 0xA0
		xcmp		%r10, 0x3F			;//   if((code - 0xA0) <= (0xDF - 0xA0)) {
		jrugt		new_pceFontGetAdrs_TAIL		;//     
		sla		%r10, 1				;//     %r10 :=                   (code - 0xA0) * sizeof(unsigned short)
		ext		KanaFive_A0_DF@ah		;//     %r11 := x = KanaFive_A0_DF[code - 0xA0]
		ext		KanaFive_A0_DF@al		;//     
		ld.uh		%r11, [%r10]			;//     
new_pceFontGetAdrs_EXPAND:					;//EXPAND:
		xld.w		%r10, fnt_buf+0x80000000	;//     %r10 := p = fnt_buf + 0x80000000	戻り値のために予め[31]をセットしておくことにしました。S1C33209のBCUは[31:28]を無視するのでfnt_bufと等価です。/usr/PIECE/system/pcekn/font.cのpceFontPut()もp[31]がセットされている事を確認した後クリアせずにアクセスしているので動作実績有りです。
		ld.w		%r12, 5				;//     %r12 := i = 5
		sla		%r11, 5				;//     %r11 := x <<= 5
new_pceFontGetAdrs_EXPAND_LOOP:					;//     do {
		ld.b		[%r10]+, %r11			;//       *p++  = x
		sra		%r11, 3				;//       %r11 := x >>= 3
		sub		%r12, 1				;//       %r12 := i--
		jrne.d		new_pceFontGetAdrs_EXPAND_LOOP	;//     } while(i)
		and		%r11, 0xFFFFFFE0		;//       %r11 := x &= ~0x1F					*delay*
		ret.d						;//   return    fnt_buf + 0x80000000
		sub		%r10, 5				;//     %r10 := fnt_buf + 0x80000000 }				*delay*
new_pceFontGetAdrs_TAIL:					;// }
		xld.w		%r10, [old_pceFontGetAdrs]	;// %r10 := old_pceFontGetAdrs
		jp		%r10				;// return  old_pceFontGetAdrs(code)
");
#endif /*PIECE*/
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#else //USE_VOLATILE_FONT
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#ifndef PIECE
/*static*/ const unsigned char* new_pceFontGetAdrs(unsigned short code) {
	if((fnt_typ & ~0x80) == 2) {	/* フォントの種類のみ比較する */
#ifdef  USE_KANAFIVE_ASCII
		if((code >= 0x20) && (code <= 0x7F)) {
			return KanaFive_20_7F[code - 0x20] + 0x80000000;
		}
#endif/*USE_KANAFIVE_ASCII*/
		if((code >= 0xA0) && (code <= 0xDF)) {
			return KanaFive_A0_DF[code - 0xA0] + 0x80000000;
		}
	}
	return (*old_pceFontGetAdrs)(code);
}
#else /*PIECE*/
asm("
		.code
		.align		1
new_pceFontGetAdrs:
		xld.ub		%r10, [fnt_typ]			;// %r10 := fnt_typ
		xand		%r10, %r10, 0x7F		;// %r10 := fnt_typ & ~0x80
		cmp		%r10, 2				;// if((fnt_typ & ~0x80) != 2) {
		jrne		new_pceFontGetAdrs_TAIL
		"
#ifdef  USE_KANAFIVE_ASCII
		"
		xsub		%r10, %r12, 0x20		;//   %r10 := code - 0x20
		xcmp		%r10, 0x5F			;//   if((code - 0x20) <= (0x7F - 0x20)) {
		jrugt		new_pceFontGetAdrs_KANA		;//     
		ld.w		%r11, 6				;//     %r11 :=                 6
		mltu.h		%r10, %r11			;//     %alr := (code - 0x20) * 6
		ld.w		%r10, %alr			;//     %r10 := (code - 0x20) * 6
		xld.w		%r11, KanaFive_20_7F+0x80000000	;//     %r11 := KanaFive_20_7F              + 0x80000000
		ret.d						;//     return  KanaFive_20_7F[code - 0x20] + 0x80000000
		add		%r10, %r11			;//     %r10 := KanaFive_20_7F              + 0x80000000 }	*delay*
		"
#endif/*USE_KANAFIVE_ASCII*/
		"
new_pceFontGetAdrs_KANA:
		xsub		%r10, %r12, 0xA0		;//   %r10 := code - 0xA0
		xcmp		%r10, 0x3F			;//   if((code - 0xA0) <= (0xDF - 0xA0)) {
		jrugt		new_pceFontGetAdrs_TAIL
		ld.w		%r11, 6				;//     %r11 :=                 6
		mltu.h		%r10, %r11			;//     %alr := (code - 0xA0) * 6
		ld.w		%r10, %alr			;//     %r10 := (code - 0xA0) * 6
		xld.w		%r11, KanaFive_A0_DF+0x80000000	;//     %r11 := KanaFive_A0_DF              + 0x80000000
		ret.d						;//     return  KanaFive_A0_DF[code - 0xA0] + 0x80000000
		add		%r10, %r11			;//     %r10 := KanaFive_A0_DF              + 0x80000000 }	*delay*
new_pceFontGetAdrs_TAIL:
		xld.w		%r10, [old_pceFontGetAdrs]	;// %r10 := old_pceFontGetAdrs
		jp		%r10				;// return  old_pceFontGetAdrs(code)
");
#endif /*PIECE*/
#endif//USE_VOLATILE_FONT
/*--------------------------------------------------------------------------*/
#ifndef PIECE
/*static*/ void new_pceFontSetType(int type) {
	fnt_typ = type;
	(*old_pceFontSetType)(type);
}
#else /*PIECE*/
asm("
		.code
		.align		1
new_pceFontSetType:
		xld.w		%r10, [old_pceFontSetType]	;// %r10 := old_pceFontSetType
		xld.b		[fnt_typ], %r12			;// fnt_typ = type			*anti-interlock*
		jp		%r10				;//         old_pceFontSetType(type)
");
#endif /*PIECE*/
//===========================================================================
#else	//↓2015/05/26変更:4x5ピクセルフォントを追加しました。
//===========================================================================
// * Tue May 26 21:15:26 JST 2015 Naoyuki Sawa
// - 4x5ピクセルフォントを追加しました。
// - P/ECEのフォントは2番までですが、pceFontGetAdrs()とpceFontSetType()をフックする事により、3番に4x5ピクセルフォントを追加しました。
//   主にrender_printf()等で使用する事を想定していますが、上記のAPIをフックしているので、pceFontPrintf()等の標準APIでも使用可能です。
// - 4x5ピクセルフォントには、「Version 0」(http://ver0.sakura.ne.jp/)さんの『極小フォント』(http://ver0.sakura.ne.jp/illust/extra-small-font.html)を使用させて頂きました。
//   それに伴い、4x6ピクセルフォントも『極小フォント』を使うように変更しました。これまで4x6ピクセルフォントは「WentWayUp: WebLog (http://wentwayup.tamaliver.jp/)」さんの、
//   『KanaFive(http://wentwayup.tamaliver.jp/e59624.html)』を使用させて頂いていたのですが、4x5ピクセルフォントと4x6ピクセルフォントの書体を統一するためです。
// - これまでは、USE_KANAFIVE_ASCIIとUSE_VOLATILE_FONTで機能の有効,無効を切り替えてコンパイルするようにしていましたが、今回の変更と同時にこれらの機能を有効に固定しました。
//   これまで使って来て、これらの機能も有用で、今後、無効にする必要は無いと判断したからです。
#define DEFINE_FONT(a,b,c,d,e) (a<<0|b<<3|c<<6|d<<9|e<<12)
static const unsigned short TBL_ExtraSmallFont[2][(0x7F-0x20+1)+(0xDF-0xA0+1)]={
//4x6
{DEFINE_FONT(0,0,0,0,0)/* 20   */,DEFINE_FONT(2,2,2,0,2)/* 21 ! */,DEFINE_FONT(5,5,0,0,0)/* 22 " */,DEFINE_FONT(5,7,5,7,5)/* 23 # */,
 DEFINE_FONT(2,6,7,3,2)/* 24 $ */,DEFINE_FONT(4,1,2,4,1)/* 25 % */,DEFINE_FONT(6,6,3,6,7)/* 26 & */,DEFINE_FONT(2,4,0,0,0)/* 27 ' */,
 DEFINE_FONT(1,2,2,2,1)/* 28 ( */,DEFINE_FONT(4,2,2,2,4)/* 29 ) */,DEFINE_FONT(5,2,7,2,5)/* 2A * */,DEFINE_FONT(0,2,7,2,0)/* 2B + */,
 DEFINE_FONT(0,0,0,2,4)/* 2C , */,DEFINE_FONT(0,0,7,0,0)/* 2D - */,DEFINE_FONT(0,0,0,0,2)/* 2E . */,DEFINE_FONT(0,1,2,4,0)/* 2F / */,
 DEFINE_FONT(2,5,7,5,2)/* 30 0 */,DEFINE_FONT(2,6,2,2,2)/* 31 1 */,DEFINE_FONT(6,1,3,4,7)/* 32 2 */,DEFINE_FONT(6,1,2,1,6)/* 33 3 */,
 DEFINE_FONT(5,5,7,1,1)/* 34 4 */,DEFINE_FONT(7,4,6,1,6)/* 35 5 */,DEFINE_FONT(2,4,6,5,2)/* 36 6 */,DEFINE_FONT(7,1,1,1,1)/* 37 7 */,
 DEFINE_FONT(2,5,2,5,2)/* 38 8 */,DEFINE_FONT(2,5,3,1,1)/* 39 9 */,DEFINE_FONT(0,2,0,2,0)/* 3A : */,DEFINE_FONT(0,2,0,2,4)/* 3B ; */,
 DEFINE_FONT(1,2,4,2,1)/* 3C < */,DEFINE_FONT(0,7,0,7,0)/* 3D = */,DEFINE_FONT(4,2,1,2,4)/* 3E > */,DEFINE_FONT(6,1,2,0,2)/* 3F ? */,
 DEFINE_FONT(6,1,3,5,2)/* 40 @ */,DEFINE_FONT(2,5,7,5,5)/* 41 A */,DEFINE_FONT(6,5,6,5,6)/* 42 B */,DEFINE_FONT(2,5,4,5,2)/* 43 C */,
 DEFINE_FONT(6,5,5,5,6)/* 44 D */,DEFINE_FONT(7,4,6,4,7)/* 45 E */,DEFINE_FONT(7,4,6,4,4)/* 46 F */,DEFINE_FONT(3,4,5,5,3)/* 47 G */,
 DEFINE_FONT(5,5,7,5,5)/* 48 H */,DEFINE_FONT(7,2,2,2,7)/* 49 I */,DEFINE_FONT(3,1,1,5,2)/* 4A J */,DEFINE_FONT(5,6,6,6,5)/* 4B K */,
 DEFINE_FONT(4,4,4,4,7)/* 4C L */,DEFINE_FONT(5,7,7,7,7)/* 4D M */,DEFINE_FONT(5,7,7,7,5)/* 4E N */,DEFINE_FONT(2,5,5,5,2)/* 4F O */,
 DEFINE_FONT(6,5,6,4,4)/* 50 P */,DEFINE_FONT(2,5,5,7,3)/* 51 Q */,DEFINE_FONT(6,5,6,6,5)/* 52 R */,DEFINE_FONT(3,4,2,1,6)/* 53 S */,
 DEFINE_FONT(7,2,2,2,2)/* 54 T */,DEFINE_FONT(5,5,5,5,7)/* 55 U */,DEFINE_FONT(5,5,5,2,2)/* 56 V */,DEFINE_FONT(7,7,7,7,2)/* 57 W */,
 DEFINE_FONT(5,5,2,5,5)/* 58 X */,DEFINE_FONT(5,5,2,2,2)/* 59 Y */,DEFINE_FONT(7,1,2,4,7)/* 5A Z */,DEFINE_FONT(3,2,2,2,3)/* 5B [ */,
 DEFINE_FONT(0,4,2,1,0)/* 5C \ */,DEFINE_FONT(6,2,2,2,6)/* 5D ] */,DEFINE_FONT(2,5,0,0,0)/* 5E ^ */,DEFINE_FONT(0,0,0,0,7)/* 5F _ */,
 DEFINE_FONT(2,1,0,0,0)/* 60 ` */,DEFINE_FONT(0,3,5,5,3)/* 61 a */,DEFINE_FONT(4,6,5,5,6)/* 62 b */,DEFINE_FONT(0,3,4,4,3)/* 63 c */,
 DEFINE_FONT(1,3,5,5,3)/* 64 d */,DEFINE_FONT(2,5,7,4,3)/* 65 e */,DEFINE_FONT(1,2,7,2,2)/* 66 f */,DEFINE_FONT(2,5,3,1,6)/* 67 g */,
 DEFINE_FONT(4,4,6,5,5)/* 68 h */,DEFINE_FONT(2,0,2,2,2)/* 69 i */,DEFINE_FONT(1,0,1,5,2)/* 6A j */,DEFINE_FONT(4,4,5,6,5)/* 6B k */,
 DEFINE_FONT(2,2,2,2,2)/* 6C l */,DEFINE_FONT(0,6,7,7,7)/* 6D m */,DEFINE_FONT(0,6,5,5,5)/* 6E n */,DEFINE_FONT(0,2,5,5,2)/* 6F o */,
 DEFINE_FONT(0,6,5,6,4)/* 70 p */,DEFINE_FONT(0,3,5,3,1)/* 71 q */,DEFINE_FONT(0,5,6,4,4)/* 72 r */,DEFINE_FONT(0,3,6,3,6)/* 73 s */,
 DEFINE_FONT(2,7,2,2,1)/* 74 t */,DEFINE_FONT(0,5,5,5,3)/* 75 u */,DEFINE_FONT(0,5,5,5,2)/* 76 v */,DEFINE_FONT(0,7,7,7,2)/* 77 w */,
 DEFINE_FONT(0,5,2,2,5)/* 78 x */,DEFINE_FONT(0,5,3,1,2)/* 79 y */,DEFINE_FONT(0,7,3,6,7)/* 7A z */,DEFINE_FONT(1,2,6,2,1)/* 7B { */,
 DEFINE_FONT(2,2,0,2,2)/* 7C | */,DEFINE_FONT(4,2,3,2,4)/* 7D } */,DEFINE_FONT(7,0,0,0,0)/* 7E ~ */,DEFINE_FONT(0,0,0,0,0)/* 7F  */,
 DEFINE_FONT(0,0,0,0,0)/* A0  */,DEFINE_FONT(0,0,0,6,6)/* A1 ｡ */,DEFINE_FONT(3,2,2,0,0)/* A2 ｢ */,DEFINE_FONT(0,0,2,2,6)/* A3 ｣ */,
 DEFINE_FONT(0,0,0,4,2)/* A4 ､ */,DEFINE_FONT(0,0,2,0,0)/* A5 ･ */,DEFINE_FONT(7,1,7,1,2)/* A6 ｦ */,DEFINE_FONT(0,0,7,3,2)/* A7 ｧ */,
 DEFINE_FONT(0,0,1,6,2)/* A8 ｨ */,DEFINE_FONT(0,2,7,5,1)/* A9 ｩ */,DEFINE_FONT(0,0,7,2,7)/* AA ｪ */,DEFINE_FONT(0,1,7,3,5)/* AB ｫ */,
 DEFINE_FONT(0,2,7,3,2)/* AC ｬ */,DEFINE_FONT(0,0,6,2,7)/* AD ｭ */,DEFINE_FONT(0,0,7,3,7)/* AE ｮ */,DEFINE_FONT(0,0,5,1,6)/* AF ｯ */,
 DEFINE_FONT(0,4,3,0,0)/* B0 ｰ */,DEFINE_FONT(7,3,2,2,4)/* B1 ｱ */,DEFINE_FONT(1,2,6,2,2)/* B2 ｲ */,DEFINE_FONT(2,7,5,1,2)/* B3 ｳ */,
 DEFINE_FONT(0,7,2,2,7)/* B4 ｴ */,DEFINE_FONT(1,7,1,3,5)/* B5 ｵ */,DEFINE_FONT(2,7,3,5,5)/* B6 ｶ */,DEFINE_FONT(2,7,2,7,2)/* B7 ｷ */,
 DEFINE_FONT(3,5,1,2,4)/* B8 ｸ */,DEFINE_FONT(4,7,2,2,4)/* B9 ｹ */,DEFINE_FONT(0,7,1,1,7)/* BA ｺ */,DEFINE_FONT(5,7,5,1,2)/* BB ｻ */,
 DEFINE_FONT(4,0,5,1,6)/* BC ｼ */,DEFINE_FONT(0,7,1,2,5)/* BD ｽ */,DEFINE_FONT(4,7,5,4,3)/* BE ｾ */,DEFINE_FONT(4,5,1,2,4)/* BF ｿ */,
 DEFINE_FONT(3,5,3,1,2)/* C0 ﾀ */,DEFINE_FONT(6,2,7,2,4)/* C1 ﾁ */,DEFINE_FONT(6,7,1,1,6)/* C2 ﾂ */,DEFINE_FONT(7,0,7,2,4)/* C3 ﾃ */,
 DEFINE_FONT(2,2,3,2,2)/* C4 ﾄ */,DEFINE_FONT(2,7,2,2,4)/* C5 ﾅ */,DEFINE_FONT(0,7,0,0,7)/* C6 ﾆ */,DEFINE_FONT(7,1,5,2,5)/* C7 ﾇ */,
 DEFINE_FONT(2,7,1,6,3)/* C8 ﾈ */,DEFINE_FONT(1,1,1,2,4)/* C9 ﾉ */,DEFINE_FONT(4,2,1,5,5)/* CA ﾊ */,DEFINE_FONT(4,7,4,4,3)/* CB ﾋ */,
 DEFINE_FONT(0,7,1,1,2)/* CC ﾌ */,DEFINE_FONT(0,2,5,5,1)/* CD ﾍ */,DEFINE_FONT(2,7,2,7,7)/* CE ﾎ */,DEFINE_FONT(7,1,5,2,1)/* CF ﾏ */,
 DEFINE_FONT(7,0,7,0,7)/* D0 ﾐ */,DEFINE_FONT(2,2,4,5,7)/* D1 ﾑ */,DEFINE_FONT(1,1,5,2,5)/* D2 ﾒ */,DEFINE_FONT(7,2,7,2,3)/* D3 ﾓ */,
 DEFINE_FONT(2,7,3,2,2)/* D4 ﾔ */,DEFINE_FONT(0,6,2,2,7)/* D5 ﾕ */,DEFINE_FONT(7,1,7,1,7)/* D6 ﾖ */,DEFINE_FONT(7,0,7,1,2)/* D7 ﾗ */,
 DEFINE_FONT(5,5,5,1,2)/* D8 ﾘ */,DEFINE_FONT(6,6,6,7,6)/* D9 ﾙ */,DEFINE_FONT(4,4,4,5,6)/* DA ﾚ */,DEFINE_FONT(0,7,5,5,7)/* DB ﾛ */,
 DEFINE_FONT(0,7,5,1,2)/* DC ﾜ */,DEFINE_FONT(4,2,0,1,6)/* DD ﾝ */,DEFINE_FONT(5,0,0,0,0)/* DE ﾞ */,DEFINE_FONT(6,6,0,0,0)/* DF ﾟ */},
//4x5
{DEFINE_FONT(0,0,0,0,0)/* 20   */,DEFINE_FONT(2,2,0,2,0)/* 21 ! */,DEFINE_FONT(5,5,0,0,0)/* 22 " */,DEFINE_FONT(5,7,5,7,0)/* 23 # */,
 DEFINE_FONT(2,7,7,2,0)/* 24 $ */,DEFINE_FONT(5,3,6,5,0)/* 25 % */,DEFINE_FONT(6,3,6,7,0)/* 26 & */,DEFINE_FONT(2,4,0,0,0)/* 27 ' */,
 DEFINE_FONT(1,2,2,1,0)/* 28 ( */,DEFINE_FONT(4,2,2,4,0)/* 29 ) */,DEFINE_FONT(2,7,2,5,0)/* 2A * */,DEFINE_FONT(2,7,2,0,0)/* 2B + */,
 DEFINE_FONT(0,0,2,4,0)/* 2C , */,DEFINE_FONT(0,7,0,0,0)/* 2D - */,DEFINE_FONT(0,0,0,2,0)/* 2E . */,DEFINE_FONT(0,1,2,4,0)/* 2F / */,
 DEFINE_FONT(7,5,5,7,0)/* 30 0 */,DEFINE_FONT(2,6,2,2,0)/* 31 1 */,DEFINE_FONT(7,1,6,7,0)/* 32 2 */,DEFINE_FONT(7,3,1,7,0)/* 33 3 */,
 DEFINE_FONT(5,5,7,1,0)/* 34 4 */,DEFINE_FONT(7,6,1,6,0)/* 35 5 */,DEFINE_FONT(4,7,5,7,0)/* 36 6 */,DEFINE_FONT(7,1,1,1,0)/* 37 7 */,
 DEFINE_FONT(7,7,5,7,0)/* 38 8 */,DEFINE_FONT(7,5,7,1,0)/* 39 9 */,DEFINE_FONT(0,2,0,2,0)/* 3A : */,DEFINE_FONT(2,0,2,4,0)/* 3B ; */,
 DEFINE_FONT(2,4,2,0,0)/* 3C < */,DEFINE_FONT(7,0,7,0,0)/* 3D = */,DEFINE_FONT(2,1,2,0,0)/* 3E > */,DEFINE_FONT(7,3,0,2,0)/* 3F ? */,
 DEFINE_FONT(7,5,7,6,0)/* 40 @ */,DEFINE_FONT(2,5,7,5,0)/* 41 A */,DEFINE_FONT(6,7,5,7,0)/* 42 B */,DEFINE_FONT(3,4,4,3,0)/* 43 C */,
 DEFINE_FONT(6,5,5,6,0)/* 44 D */,DEFINE_FONT(7,4,6,7,0)/* 45 E */,DEFINE_FONT(7,4,6,4,0)/* 46 F */,DEFINE_FONT(3,4,5,3,0)/* 47 G */,
 DEFINE_FONT(5,5,7,5,0)/* 48 H */,DEFINE_FONT(7,2,2,7,0)/* 49 I */,DEFINE_FONT(1,1,5,2,0)/* 4A J */,DEFINE_FONT(5,6,6,5,0)/* 4B K */,
 DEFINE_FONT(4,4,4,7,0)/* 4C L */,DEFINE_FONT(5,7,7,7,0)/* 4D M */,DEFINE_FONT(7,5,5,5,0)/* 4E N */,DEFINE_FONT(2,5,5,2,0)/* 4F O */,
 DEFINE_FONT(6,5,6,4,0)/* 50 P */,DEFINE_FONT(2,5,7,3,0)/* 51 Q */,DEFINE_FONT(6,5,6,5,0)/* 52 R */,DEFINE_FONT(7,6,3,7,0)/* 53 S */,
 DEFINE_FONT(7,2,2,2,0)/* 54 T */,DEFINE_FONT(5,5,5,7,0)/* 55 U */,DEFINE_FONT(5,5,5,2,0)/* 56 V */,DEFINE_FONT(7,7,7,2,0)/* 57 W */,
 DEFINE_FONT(5,2,2,5,0)/* 58 X */,DEFINE_FONT(5,5,2,2,0)/* 59 Y */,DEFINE_FONT(7,3,6,7,0)/* 5A Z */,DEFINE_FONT(3,2,2,3,0)/* 5B [ */,
 DEFINE_FONT(0,4,2,1,0)/* 5C \ */,DEFINE_FONT(6,2,2,6,0)/* 5D ] */,DEFINE_FONT(2,5,0,0,0)/* 5E ^ */,DEFINE_FONT(0,0,0,7,0)/* 5F _ */,
 DEFINE_FONT(2,1,0,0,0)/* 60 ` */,DEFINE_FONT(0,3,5,3,0)/* 61 a */,DEFINE_FONT(4,6,5,6,0)/* 62 b */,DEFINE_FONT(0,3,4,3,0)/* 63 c */,
 DEFINE_FONT(1,3,5,3,0)/* 64 d */,DEFINE_FONT(3,7,4,3,0)/* 65 e */,DEFINE_FONT(1,2,7,2,0)/* 66 f */,DEFINE_FONT(7,7,1,3,0)/* 67 g */,
 DEFINE_FONT(4,6,5,5,0)/* 68 h */,DEFINE_FONT(2,0,2,2,0)/* 69 i */,DEFINE_FONT(1,0,5,2,0)/* 6A j */,DEFINE_FONT(4,5,6,5,0)/* 6B k */,
 DEFINE_FONT(6,2,2,2,0)/* 6C l */,DEFINE_FONT(0,6,7,7,0)/* 6D m */,DEFINE_FONT(0,6,5,5,0)/* 6E n */,DEFINE_FONT(0,2,5,2,0)/* 6F o */,
 DEFINE_FONT(0,7,7,4,0)/* 70 p */,DEFINE_FONT(0,7,7,1,0)/* 71 q */,DEFINE_FONT(0,5,6,4,0)/* 72 r */,DEFINE_FONT(0,3,2,6,0)/* 73 s */,
 DEFINE_FONT(2,7,2,1,0)/* 74 t */,DEFINE_FONT(0,5,5,3,0)/* 75 u */,DEFINE_FONT(0,5,5,2,0)/* 76 v */,DEFINE_FONT(0,7,7,2,0)/* 77 w */,
 DEFINE_FONT(0,5,2,5,0)/* 78 x */,DEFINE_FONT(0,5,3,1,0)/* 79 y */,DEFINE_FONT(0,6,2,3,0)/* 7A z */,DEFINE_FONT(3,6,2,3,0)/* 7B { */,
 DEFINE_FONT(2,2,2,2,0)/* 7C | */,DEFINE_FONT(6,3,2,6,0)/* 7D } */,DEFINE_FONT(7,0,0,0,0)/* 7E ~ */,DEFINE_FONT(0,0,0,0,0)/* 7F  */,
 DEFINE_FONT(0,0,0,0,0)/* A0  */,DEFINE_FONT(0,0,6,6,0)/* A1 ｡ */,DEFINE_FONT(3,2,0,0,0)/* A2 ｢ */,DEFINE_FONT(0,0,2,6,0)/* A3 ｣ */,
 DEFINE_FONT(0,0,4,2,0)/* A4 ､ */,DEFINE_FONT(0,2,0,0,0)/* A5 ･ */,DEFINE_FONT(7,7,1,2,0)/* A6 ｦ */,DEFINE_FONT(0,7,3,2,0)/* A7 ｧ */,
 DEFINE_FONT(0,1,6,2,0)/* A8 ｨ */,DEFINE_FONT(0,2,7,1,0)/* A9 ｩ */,DEFINE_FONT(0,0,2,7,0)/* AA ｪ */,DEFINE_FONT(0,1,7,3,0)/* AB ｫ */,
 DEFINE_FONT(0,7,5,4,0)/* AC ｬ */,DEFINE_FONT(0,6,2,7,0)/* AD ｭ */,DEFINE_FONT(0,7,3,7,0)/* AE ｮ */,DEFINE_FONT(0,5,1,6,0)/* AF ｯ */,
 DEFINE_FONT(0,4,3,0,0)/* B0 ｰ */,DEFINE_FONT(7,3,2,4,0)/* B1 ｱ */,DEFINE_FONT(1,6,2,2,0)/* B2 ｲ */,DEFINE_FONT(2,7,1,2,0)/* B3 ｳ */,
 DEFINE_FONT(0,7,2,7,0)/* B4 ｴ */,DEFINE_FONT(1,7,3,5,0)/* B5 ｵ */,DEFINE_FONT(2,7,3,5,0)/* B6 ｶ */,DEFINE_FONT(7,2,7,2,0)/* B7 ｷ */,
 DEFINE_FONT(3,5,1,2,0)/* B8 ｸ */,DEFINE_FONT(4,7,2,4,0)/* B9 ｹ */,DEFINE_FONT(7,1,1,7,0)/* BA ｺ */,DEFINE_FONT(5,7,5,1,0)/* BB ｻ */,
 DEFINE_FONT(4,1,5,2,0)/* BC ｼ */,DEFINE_FONT(7,1,2,5,0)/* BD ｽ */,DEFINE_FONT(7,5,4,3,0)/* BE ｾ */,DEFINE_FONT(5,1,2,4,0)/* BF ｿ */,
 DEFINE_FONT(3,7,1,2,0)/* C0 ﾀ */,DEFINE_FONT(6,2,7,2,0)/* C1 ﾁ */,DEFINE_FONT(6,7,1,2,0)/* C2 ﾂ */,DEFINE_FONT(7,0,7,2,0)/* C3 ﾃ */,
 DEFINE_FONT(2,2,3,2,0)/* C4 ﾄ */,DEFINE_FONT(2,7,2,4,0)/* C5 ﾅ */,DEFINE_FONT(3,0,0,7,0)/* C6 ﾆ */,DEFINE_FONT(7,5,2,5,0)/* C7 ﾇ */,
 DEFINE_FONT(7,1,6,3,0)/* C8 ﾈ */,DEFINE_FONT(1,1,2,4,0)/* C9 ﾉ */,DEFINE_FONT(2,1,5,5,0)/* CA ﾊ */,DEFINE_FONT(4,7,4,3,0)/* CB ﾋ */,
 DEFINE_FONT(7,1,1,2,0)/* CC ﾌ */,DEFINE_FONT(2,5,5,1,0)/* CD ﾍ */,DEFINE_FONT(7,2,7,7,0)/* CE ﾎ */,DEFINE_FONT(7,1,5,2,0)/* CF ﾏ */,
 DEFINE_FONT(7,7,0,7,0)/* D0 ﾐ */,DEFINE_FONT(2,4,5,7,0)/* D1 ﾑ */,DEFINE_FONT(1,5,2,5,0)/* D2 ﾒ */,DEFINE_FONT(7,7,2,3,0)/* D3 ﾓ */,
 DEFINE_FONT(2,7,3,2,0)/* D4 ﾔ */,DEFINE_FONT(6,2,2,7,0)/* D5 ﾕ */,DEFINE_FONT(7,1,3,7,0)/* D6 ﾖ */,DEFINE_FONT(7,0,7,1,0)/* D7 ﾗ */,
 DEFINE_FONT(5,5,1,2,0)/* D8 ﾘ */,DEFINE_FONT(6,6,7,6,0)/* D9 ﾙ */,DEFINE_FONT(4,4,5,6,0)/* DA ﾚ */,DEFINE_FONT(0,7,5,7,0)/* DB ﾛ */,
 DEFINE_FONT(7,5,1,2,0)/* DC ﾜ */,DEFINE_FONT(4,0,1,6,0)/* DD ﾝ */,DEFINE_FONT(5,0,0,0,0)/* DE ﾞ */,DEFINE_FONT(6,6,0,0,0)/* DF ﾟ */}};
#undef  DEFINE_FONT
/*--------------------------------------------------------------------------*/
/*static*/ const unsigned char* new_pceFontGetAdrs(unsigned short code);
/*static*/ void new_pceFontSetType(int type);
static const unsigned char* (*old_pceFontGetAdrs)(unsigned short code) = new_pceFontGetAdrs;
static void                 (*old_pceFontSetType)(int type           ) = new_pceFontSetType;
static unsigned char fnt_typ    __attribute__((unused))/*asmブロックから参照*/;	//\usr\PIECE\sysdev\pcekn\work.hのfnt_typと同じ値を保持
static unsigned char fnt_buf[6] __attribute__((unused))/*asmブロックから参照*/;	//フォント展開バッファ
/*--------------------------------------------------------------------------*/
static void hook_unhook_font() {
	old_pceFontGetAdrs = (PCEKSENT)pceVectorSetKs(KSNO_FontGetAdrs, old_pceFontGetAdrs);
	old_pceFontSetType = (PCEKSENT)pceVectorSetKs(KSNO_FontSetType, old_pceFontSetType);
}
/*--------------------------------------------------------------------------*/
#ifndef PIECE
/*static*/ const unsigned char* new_pceFontGetAdrs(unsigned short code) {
	const unsigned short* p;
	unsigned char* q;
	int i, x;
	i = (fnt_typ & ~0x80/*フォントの種類のみ比較する*/) - 2;
	if((unsigned)i >  1) { goto L_OLD; }	//i=0:4x6,i=1:4x5
	p  = TBL_ExtraSmallFont[i];
	i  = (code - 0x20);
	if((unsigned)i <= (0x7F - 0x20)) { goto L_NEW; }
	p += (0x80 - 0x20);
	i -= (0xA0 - 0x20);
	if((unsigned)i >  (0xDF - 0xA0)) { goto L_OLD; }
L_NEW:
	x  = p[i] << 5;
	i  = 5;
	q  = fnt_buf + 0x80000000;	//戻り値のために予め[31]をセットしておくことにしました。S1C33209のBCUは[31:28]を無視するのでfnt_bufと等価です。/usr/PIECE/system/pcekn/font.cのpceFontPut()もp[31]がセットされている事を確認した後クリアせずにアクセスしているので動作実績有りです。
	do {
		*q++ = x;
		x = (x >> 3) & ~0x1F;
	} while(--i);
	return q - 5;
L_OLD:
	return (*old_pceFontGetAdrs)(code);
}
#else //PIECE
asm("
		.code
		.align		1
new_pceFontGetAdrs:
		xld.ub		%r4, [fnt_typ]			;//%r4  := i  = fnt_typ
		xand		%r4, %r4, 0x7F			;//%r4  := i &= ~0x80
		sub		%r4, 2				;//%r4  := i -= 2
		cmp		%r4, 1				;//if((unsigned)i >  1) { goto L_OLD }
		jrugt		new_pceFontGetAdrs_OLD		;//
		xld.w		%r5, TBL_ExtraSmallFont		;//%r5    := p  = TBL_ExtraSmallFont[0]
		jrne.d		new_pceFontGetAdrs_L10		;//if((unsigned)i == 1) {
		ld.w		%r4, %r12			;//%r4  := i  = code					*delay*
		xadd		%r5, %r5, 0x140			;//  %r5  := p  = TBL_ExtraSmallFont[1]
new_pceFontGetAdrs_L10:						;//}
		sub		%r4, 0x20			;//%r4  := i -= 0x20
		xcmp		%r4, 0x5F			;//if((unsigned)i <= (0x7F - 0x20)) { goto L_NEW }
		jrule		new_pceFontGetAdrs_NEW		;//
		xadd		%r5, %r5, 0xC0			;//%r5  := p += (0x80 - 0x20)
		xsub		%r4, %r4, 0x80			;//%r4  := i -= (0xA0 - 0x20)
		xcmp		%r4, 0x3F			;//if((unsigned)i >  (0xDF - 0xA0)) { goto L_OLD }
		jrugt		new_pceFontGetAdrs_OLD		;//
new_pceFontGetAdrs_NEW:						;//
		add		%r5, %r4			;//%r5  :=      &p[i]
		add		%r5, %r4			;//
		ld.uh		%r5, [%r5]			;//%r5  := x   = p[i]
		ld.w		%r4, 5				;//%r4  := i   = 5					*anti-interlock*
		sla		%r5, 5				;//%r5  := x <<= 5
		xld.w		%r10, fnt_buf+0x80000000	;//%r10 := q = fnt_buf + 0x80000000	戻り値のために予め[31]をセットしておくことにしました。S1C33209のBCUは[31:28]を無視するのでfnt_bufと等価です。/usr/PIECE/system/pcekn/font.cのpceFontPut()もp[31]がセットされている事を確認した後クリアせずにアクセスしているので動作実績有りです。
new_pceFontGetAdrs_LOOP:					;//do {
		ld.b		[%r10]+, %r5			;//  *q++  = x
		sra		%r5, 3				;//  %r5  := x >>= 3
		sub		%r4, 1				;//  %r4  := i--
		jrne.d		new_pceFontGetAdrs_LOOP		;//} while(i)
		and		%r5, 0xFFFFFFE0			;//  %r5  := x  &= ~0x1F				*delay*
		ret.d						;//return  fnt_buf + 0x80000000
		sub		%r10, 5				;//%r10 := fnt_buf + 0x80000000				*delay*
new_pceFontGetAdrs_OLD:						;//
		xld.w		%r4, [old_pceFontGetAdrs]	;//%r4  := old_pceFontGetAdrs
		jp		%r4				;//return  old_pceFontGetAdrs(code)
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
/*static*/ void new_pceFontSetType(int type) {
	fnt_typ = type;
	(*old_pceFontSetType)(type);	//P/ECEカーネルはフォントタイプ3以上を2と同じと見なすので3を渡しても安全です。/usr/PIECE/sysdev/pcekn/font.cの_pceFontSetType()を参照してください。
}
//===========================================================================
#endif	//}}2015/05/26変更:4x5ピクセルフォントを追加しました。
/****************************************************************************
 *	高精度パッドセンシング
 ****************************************************************************/
// * Sun Jan 04 01:02:41 JST 2015 Naoyuki Sawa
//「高精度パッドセンシング(タイマ割り込み版)」と「高精度パッドセンシング(入力割り込み版)」のどちらを使用するかを選択します。
//・「高精度パッドセンシング(タイマ割り込み版)」の利点は、長期間の動作実績が有り安定している事とコードサイズが小さい事です。欠点は、やや負荷が高い事です。
//・「高精度パッドセンシング(入力割り込み版)」の利点は、すこし負荷を低減した事です。欠点は、動作検証がまだ充分ではない事とややコードサイズが大きい事です。
// * Sun Jan 04 02:13:33 JST 2015 Naoyuki Sawa
// - 負荷の低減を計測しました。
//   「高精度パッドセンシング(タイマ割り込み版)」 … 0.5%
//   「高精度パッドセンシング(入力割り込み版)」   … 0.0% 理論上もボタン操作を行わない限り更新処理は発生しないので負荷0.0%という結果で予定通りであり問題無く動作しているようです。
// - コードサイズの増加を調べました。
//   「高精度パッドセンシング(タイマ割り込み版)」⇒「高精度パッドセンシング(入力割り込み版)」で、584バイトの増加でした。かなり多めです。
//   生成されたアセンブラコードを見たところ、内蔵レジスタのアドレス指定に無駄が有るようなので、Asm化すれば容量削減できるかも知れません。
// * Sun Jan 04 04:17:47 JST 2015 Naoyuki Sawa
// - _precision_pad_isr()をAsm化して容量削減しました。
//   「高精度パッドセンシング(タイマ割り込み版)」⇒「高精度パッドセンシング(入力割り込み版)」で、432バイトの増加になりました。
#define USE_INPUT_INTERRUPT_PRECISION_PAD	//この行を有効にすると「高精度パッドセンシング(入力割り込み版)」を使用する。
#ifndef USE_INPUT_INTERRUPT_PRECISION_PAD
/*==========================================================================*
 *	高精度パッドセンシング(タイマ割り込み版)
 *==========================================================================*/
// * Sun Dec 30 15:29:56 JST 2007 Naoyuki Sawa
// - パッドの反応を良くするために、pcePadGetDirect()をフックして処理を加える対策を行いました。
//   カーネルもフック経由でpcePadGetDirect()を利用しているので、これでpcePadGet()も改善されます。
// - 対策前は、pceAppProc()が長時間かかった場合に、パッドの反応が極端に悪くなる場合がありました。
//   理由は、パッドセンスの間隔が空き過ぎて、一瞬の押下や、一瞬の解放を読み逃してしまうからです。
// - 対策後は、16ms(=60Hz)のタイマコールバックでパッドセンスを行うようにしました。
//   前回のpcePadGetProc()から押下状態の変化を検出すると、次回のpcePadGetProc()までそれを維持します。
//   具体的な例は、下図を参照してください。
//   この対策によって、押下/解放トリガを見逃すことが少なくなり、操作性が向上しました。
//
//	[対策前]
//
//		ボタン押下		　　　┌──┐　　　　　　┌──┐　　　　　　　　
//		ボタン解放		───┘　　└──────┘　　└────────
//		pceAppProc()		┤　├────────┤　├────────┤　├
//		pcePadGetProc()		　↓　　　　　　　　　　↓　　　　　　　　　　↓
//					　０　　　　　　　　　　０　　　　　　　　　　０　
//
//	[対策後]
//
//		ボタン押下		　　　┌──┐　　　　　　┌──┐　　　　　　　　
//		ボタン解放		───┘　　└──────┘　　└────────
//		precision_pad_proc()	（↓）　↓←16ms→↓　（↓）↓　　　　↓　　（↓）
//		precision_pad.next	００００１１１１１１１１１１１１１１１００００００
//		pceAppProc()		┤　├────────┤　├────────┤　├
//		pcePadGetProc()		　↓　　　　　　　　　　↓　　　　　　　　　　↓
//		precision_pad.prev	００００００００００００１１１１１１１１１１１００
//					　０　　　　　　　　　　１　　　　　　　　　　０　
//
// - タイマコールバックを16ms(=60Hz)に設定した理由は、絶対ではないので、調整可能です
//   一般的なゲームが60Hzでセンスし、操作性に問題無いので、これ以上の頻度は不要だと思います。
//   頻度を下げるほど負荷も下がるので、30～50Hz程度にしてもっと負荷を下げることもできます。
//   ただし60Hzのままでも、システム全体への負荷は0.05%未満程度なので、これで充分だと思います。
// - タイマでポーリングを行う方法以外に、入力割り込みによる監視も検討しましたが、ダメでした。	┐
//   入力割り込みで監視ができればもっと負荷を下げられるのですが、以下の理由で採用できません。	├→{{2015/01/04追記:再検討したところキー入力割り込みとポート入力割り込みを併用することで可能でした。「高精度パッドセンシング(入力割り込み版)」を実装しました。}}
//   カーネルの使用するポート入力割り込み1と3が、左ボタンと下ボタンと被っていて、使えません。	│
//   キー入力割り込み1が空いていますが、組み合わせの制限のためすべてのキーを監視できません。	┘
// - タイマコールバックは、timer_start()ではなく、pceTimerSetCallback()を直接利用しました。
//   このようにした理由は、以下のとおりです。
//   pceTimerSetCallback()のチャネルは貴重なので、本来はtimer_start()を使いたいところです。
//   しかし、timer_start()は負荷が大きく、1チャネルでも使えば3%近いシステム負荷となります。
//   今回の対策は、アプリケーションによってOn/Offするものでなく、常に適用するものなので、
//   負荷低減を優先して、pceTimerSetCallback()を直接利用することにしました。
//   pceTimerSetCallback()を直接利用した場合の負荷は、前述のとおり、0.05%未満程度です。
//   この程度の負荷ならば、すべてのアプリケーションに影響しても、許容範囲だと思います。
// - 今回の対策のために、pceTimerSetCallback()のチャネル1を占有しました。
//   チャネル0は、既に、timer_start()が占有しています。
//   今後、アプリケーションが直接利用できるpceTimerSetCallback()のチャネルは2以降となります。
//   アプリケーションはなるべく、pceTimerSetCallback()よりも、timer_start()を使ってください。
// * Sat Dec 27 14:10:18 JST 2014 Naoyuki Sawa
// - 高精度パッドセンシングが「8ビットプログラマブルタイマ割り込みの一元管理」を使用するように変更しました。
//   負荷が高くなりました。変更前=約0.05%⇒変更後=約0.5%
//   負荷が10倍も高くなりましたが、約0.5%ならばまだ許容範囲だと思います。
// - 上記のデメリットにもかかわらず変更した理由は、高い割り込み優先度で高精度パッドセンシングを行うためです。
//   これまでは(IL=1)で高精度パッドセンシングを行っていたため、長時間(IL=1)がマスクされるとトリガの取りこぼしが発生しました。
//   長時間(IL=1)がマスクされる主な要因は、GCが起動した時です。
//   即ちこれまでは、GCを使用するアプリケーションで、ボタンを押したのに反応しない問題が時々発生していました。
//   今後は(IL=6)で高精度パッドセンシングを行うので、GCを使用するアプリケーションでもトリガの取りこぼしが発生しなくなります。
/*--------------------------------------------------------------------------*/
static struct {
	unsigned long (*old_pcePadGetDirect)();	/* +0,4: */
	         unsigned char prev;		/* +4,1: 前回、pcePadGetDirect()に返した値 */
	volatile unsigned char next;		/* +5,1: 次回、pcePadGetDirect()に返す値 */
    //{{2014/12/27追加:高精度パッドセンシングが「8ビットプログラマブルタイマ割り込みの一元管理」を使用するように変更しました。
	short e;				/* +6,2: precision_pad_callback()が60Hzのタイミングを計測するための誤差項。 */
    //}}2014/12/27追加:高精度パッドセンシングが「8ビットプログラマブルタイマ割り込みの一元管理」を使用するように変更しました。
} precision_pad;
/*--------------------------------------------------------------------------*/
#if 0
static unsigned long new_pcePadGetDirect() {
ENTER_CS;
	/* - 割り込みを禁止したままのループの中で、キー状態検出のために繰り返しpcePadGetDirect()が呼ばれた場合のために、
	 *   少なくとも一度は、precision_pad_proc()を実行しておきます。
	 *   もし「precision_pad.prev = precision_pad.next;」とすると、上述のようなケースにおいて、
	 *   precision_pad_proc()がコールバックされず、pcePadGetDirect()が返す値が変化しなくなってしまうので、ダメです。
	 * - precision_pad_proc()がprecision_pad.nextを更新した値を、そのまま返すように作成してあるので、
	 *   「precision_pad_proc(); precision_pad.prev = precision_pad.next;」と同じ処理を、効率的に実装できました。
	 * - precision_pad_proc()がprecision_pad.nextを更新した後、new_pcePadGetDirect()が戻り値をprecision_pad.prevに
	 *   格納する前に割り込みが発生して、precision_pad_proc()がprecision_pad.prevを参照してしまうことを避けるために、
	 *   ENTER_CS～LEAVE_CSが必須です。
	 */
	precision_pad.prev = precision_pad_proc();	/* 「precision_pad.prev = precision_pad.next;」ではダメ!! */
LEAVE_CS;
	return precision_pad.prev;
}
#else /*↓アセンブラ化して、高速化、省メモリ化しました。*/
/*static*/ unsigned long new_pcePadGetDirect();
asm("
	.code
	.align	1
new_pcePadGetDirect:
	xsub	%sp, %sp, 4
	ld.w	%r12, %sp			; %r12 := &__cs__
	xcall	pceSyncEnterCriticalSection	; ENTER_CS
	xcall	precision_pad_proc		; %r10 := precision_pad_proc()
	xld.b	[precision_pad+4], %r10		; prev := precision_pad_proc()
	ld.w	%r12, %sp			; %r12 := &__cs__
	xcall	pceSyncLeaveCriticalSection	; LEAVE_CS
	xld.ub	%r10, [precision_pad+4]		; %r10 := prev
	xadd	%sp, %sp, 4
	ret					; return prev
");
#endif
/*--------------------------------------------------------------------------*/
#if 0
static /*void*/int precision_pad_proc() {
	int pad = precision_pad.old_pcePadGetDirect();
	int mask = (precision_pad.prev ^ pad);
	precision_pad.next = (precision_pad.next & ~mask) | (pad & mask);
	/* - new_pcePadGetDirect()の実装を効率化するために、precision_pad.nextの値を返します。
	 * //{{2014/12/27コメント変更:高精度パッドセンシングが「8ビットプログラマブルタイマ割り込みの一元管理」を使用するように変更しました。
	 * //pceTimerSetCallback()のコールバック関数として呼ばれたときは、戻り値はダミーです。
	 * //↓2014/12/27コメント変更:高精度パッドセンシングが「8ビットプログラマブルタイマ割り込みの一元管理」を使用するように変更しました。
	 *   precision_pad_callback()から呼ばれたときは、戻り値はダミーです。
	 * //}}2014/12/27コメント変更:高精度パッドセンシングが「8ビットプログラマブルタイマ割り込みの一元管理」を使用するように変更しました。
	 */
	return precision_pad.next;
}
#else /*↓アセンブラ化して、高速化、省メモリ化しました。*/
/*static*/ /*void*/int precision_pad_proc();
asm("
	.code
	.align	1
precision_pad_proc:
	xld.w	%r10, [precision_pad]		; %r10 := old_pcePadGetDirect
	call	%r10				; %r10 := pad = old_pcePadGetDirect()
	xld.w	%r11, precision_pad+4		; %r11 := &prev
	ld.ub	%r12, [%r11]+			; %r12 := prev, %r11 := &next
	ld.ub	%r13, [%r11]			; %r13 := next
	xor	%r12, %r10			; %r12 := mask = (prev ^ pad)
	and	%r10, %r12			; %r10 := (pad & mask)
	not	%r12, %r12			; %r12 := ~mask
	and	%r13, %r12			; %r13 := (next & ~mask)
	or	%r10, %r13			; %r10 := (next & ~mask) | (pad & mask)
	ld.b	[%r11], %r10			; next := (next & ~mask) | (pad & mask)
	ret					; return next
");
#endif
//{{2014/12/27変更:高精度パッドセンシングが「8ビットプログラマブルタイマ割り込みの一元管理」を使用するように変更しました。
///*--------------------------------------------------------------------------*/
//static void precision_pad_init() {
//	precision_pad.old_pcePadGetDirect = pceVectorSetKs(KSNO_PadGetDirect, new_pcePadGetDirect);
//	pceTimerSetCallback(1/*変更可*/, PCE_TT_PERIODIC, 16/*調整可*/, (void (*)())precision_pad_proc);
//}
///*--------------------------------------------------------------------------*/
//static void precision_pad_free() {
//	/* pceTimerSetCallback(0-3)は、既にまとめて停止しているので、ここで停止する必要はありません。 */
//	pceVectorSetKs(KSNO_PadGetDirect, precision_pad.old_pcePadGetDirect);
//}
//↓2014/12/27変更:高精度パッドセンシングが「8ビットプログラマブルタイマ割り込みの一元管理」を使用するように変更しました。
/*--------------------------------------------------------------------------*/
#ifdef PIECE
static void precision_pad_callback() {
	//割り込みが何回発生で60Hzに相当するか:
	//θ/Hz/プリスケーラ分周比/タイマ分周比 = 24000000/60/256/256=400000/256/256=3125/512[回] …24MHz時
	//                                      = 48000000/60/256/256=800000/256/256=3125/256[回] …48MHz時
	if((precision_pad.e -= ((bP0_P0D_P07D + 1) << 8)) >= 0) { return; }
	//                       ~~~~~~~~~~~~              1:24MHz,  0:48MHz
	//                      ~~~~~~~~~~~~~~~~~~         2:24MHz,  1:48MHz
	//                     ~~~~~~~~~~~~~~~~~~~~~~~~~ 512:24MHz,256:48MHz
	precision_pad.e += 3125;
	precision_pad_proc();
}
#else //PIECE
/*static*/ void precision_pad_callback();
asm("
		.code
		.align		1
precision_pad_callback:
		xld.w		%r4, precision_pad+6		;//%r4  :=    &precision_pad.e
		xld.ub		%r6, [0x402d1]			;//%r6  :=       pP0_P0D
		ld.h		%r5, [%r4]			;//%r5  := e = precision_pad.e			*anti-interlock*
		sra		%r6, 7				;//%r6  :=       bP0_P0D_P07D
		add		%r6, 1				;//%r6  :=       bP0_P0D_P07D + 1
		sla		%r6, 8				;//%r6  :=      (bP0_P0D_P07D + 1) << 8
		sub		%r5, %r6			;//%r5  := e -= (bP0_P0D_P07D + 1) << 8
		jrlt		precision_pad_callback_L10	;//if(e >= 0) {
		ld.h		[%r4], %r5			;//  precision_pad.e = e
		ret						;//  return
precision_pad_callback_L10:					;//}
		xadd		%r5, %r5, 3125			;//%r5  := e += 3125
		ld.h		[%r4], %r5			;//precision_pad.e = e
		xjp		precision_pad_proc		;//precision_pad_proc()
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
static void precision_pad_init() {
ENTER_CS;
	precision_pad.old_pcePadGetDirect = pceVectorSetKs(KSNO_PadGetDirect, new_pcePadGetDirect);
	//--- 8-bit timer 0 ---
	//8-bit timer control register
	pT8_CTL0		= 0;			//タイマを停止する。
	bCLKCTL_T8_01_P8TON0	= 0;			//プリスケーラを停止する。
	bCLKSEL_T8_P8TPCK0	= 0;			//プリスケーラクロック選択 = 分周クロック
	bCLKCTL_T8_01_P8TS0	= CLKDIV_8T03_256;	//プリスケーラクロック分周比選択 = θ/256
	pT8_RLD0		= -1;			//タイマ分周比 = 256
	bCLKCTL_T8_01_P8TON0	= 1;			//プリスケーラを開始する。
	pT8_CTL0		= 3/*PTRUN|PSET*/;	//タイマを開始する。
	install_8tu_interrupt_callback(0, precision_pad_callback);
LEAVE_CS;
}
/*--------------------------------------------------------------------------*/
static void precision_pad_free() {
ENTER_CS;
	pceVectorSetKs(KSNO_PadGetDirect, precision_pad.old_pcePadGetDirect);
	//--- 8-bit timer 0 ---
	install_8tu_interrupt_callback(0, NULL);
LEAVE_CS;
}
//}}2014/12/27変更:高精度パッドセンシングが「8ビットプログラマブルタイマ割り込みの一元管理」を使用するように変更しました。
#else //USE_INPUT_INTERRUPT_PRECISION_PAD
/*==========================================================================*
 *	高精度パッドセンシング(入力割り込み版)
 *==========================================================================*/
#define PRECISION_PAD_CHATTERING_WAIT_MS	10	//チャタリング安定待ち時間[ms]	(調整可)
/*--------------------------------------------------------------------------*/
static struct {
	unsigned long (*old_pcePadGetDirect)();		//+0,4
	         unsigned char prev;			//+4,1 前回、pcePadGetDirect()に返した値
	volatile unsigned char next;			//+5,1 次回、pcePadGetDirect()に返す値
	         unsigned char chattering_wait;		//+6,1 チャタリング安定待ち時間[ms]
} precision_pad;
/*--------------------------------------------------------------------------*/
#ifndef PIECE
static /*void*/int precision_pad_proc() {	//IL≧6,IE=?
	int pad = precision_pad.old_pcePadGetDirect();
	int mask = (precision_pad.prev ^ pad);
	precision_pad.next = (precision_pad.next & ~mask) | (pad & mask);
	return precision_pad.next;
}
#else //PIECE
/*static*/ /*void*/int precision_pad_proc();	//IL≧6,IE=?
asm("
		.code
		.align		1
precision_pad_proc:
		xld.w		%r10, [precision_pad]		;//%r10 :=       old_pcePadGetDirect
		call		%r10				;//%r10 := pad = old_pcePadGetDirect()
		xld.w		%r11, precision_pad+4		;//%r11 :=       &precision_pad.prev
		ld.ub		%r12, [%r11]+			;//%r12 := prev = precision_pad.prev, %r11 := &precision_pad.next
		ld.ub		%r13, [%r11]			;//%r13 := next = precision_pad.next
		xor		%r12, %r10			;//%r12 := mask = (prev ^ pad)
		and		%r10, %r12			;//%r10 :=                                       (pad & mask)
		not		%r12, %r12			;//%r12 :=                              ~mask
		and		%r13, %r12			;//%r13 :=                      (next & ~mask)
		or		%r10, %r13			;//%r10 :=                      (next & ~mask) | (pad & mask)
		ld.b		[%r11], %r10			;//       precision_pad.next := (next & ~mask) | (pad & mask)
		ret						;//return precision_pad.next
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
#ifndef PIECE
static unsigned long new_pcePadGetDirect() {
ENTER_IL(6);
	precision_pad.prev = precision_pad_proc();
LEAVE_IL;
	return precision_pad.prev;
}
#else //PIECE
/*static*/ unsigned long new_pcePadGetDirect();
asm("
		.code
		.align		1
new_pcePadGetDirect:
		pushn		%r0
		xcall.d		EnterIL				;//%r10 := __il__ = EnterIL(6)
		ld.w		%r12, 6				;//                         6			*delay*
		xcall.d		precision_pad_proc		;//%r10 :=               precision_pad_proc()
		ld.w		%r0, %r10			;//%r0  := __il__				*delay*
		xld.b		[precision_pad+4], %r10		;//precision_pad.prev := precision_pad_proc()
		xcall.d		LeaveIL				;//LeaveIL(__il__)
		ld.w		%r12, %r0			;//%r12 := __il__				*delay*
		xld.ub		%r10, [precision_pad+4]		;//%r10 := precision_pad.prev
		popn		%r0				;//
		ret						;//return  precision_pad.prev
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
#ifndef PIECE
/*static*/ void precision_pad_isr();		//IL=6,IE=0
asm("
		.code
		.align		1
precision_pad_isr:
		pushn		%r15
		ld.w		%r0, %alr
		ld.w		%r1, %ahr
		xcall		_precision_pad_isr
		ld.w		%ahr, %r1
		ld.w		%alr, %r0
		popn		%r15
		reti
");
static void _precision_pad_isr() {		//IL=6,IE=0
	//--- Port input interrupt ---
	//Key input 0-1, port input 0-3 interrupt request flag register
	//Port 4-7, clock timer, A/D interrupt request flag register
	bRST_RESET_RSTONLY		= 1;				//Interrupt factor flag reset method selection
	pINT_FK01_FP03			= 0x30;				//Key input 0, Key input 1
	pINT_FP47_FCT_FAD		= 0x0C;				//Port input 4, Port input 5
	bRST_RESET_RSTONLY		= 0;				//Interrupt factor flag reset method selection
	//Key input interrupt input comparison register
do {																		//┐
	pKINTCOMP_SCPK0			= (pK5_K5D & 0x18/*K5[4:3]*/);	//Key input interrupt (FPK0) input comparison register (0x402cc)	//├設定値とポート値が確実に整合するまで繰り返します。ポート値を取得して設定する間にポート値が変化した場合は繰り返します。エッジトリガなのでこうしないと割り込みが発生しなくなってしまう恐れがあります。
} while(pKINTCOMP_SCPK0		       != (pK5_K5D & 0x18/*K5[4:3]*/));										//┘
do {																		//┐
	pKINTCOMP_SCPK1			= (pK6_K6D & 0x0F/*K6[3:0]*/);	//Key input interrupt (FPK1) input comparison register (0x402cd)	//├設定値とポート値が確実に整合するまで繰り返します。ポート値を取得して設定する間にポート値が変化した場合は繰り返します。エッジトリガなのでこうしないと割り込みが発生しなくなってしまう恐れがあります。
} while(pKINTCOMP_SCPK1		       != (pK6_K6D & 0x0F/*K6[3:0]*/));										//┘
	//Port input interrupt input polarity select register
do {																		//┐
	bPINTPOL_SPPT_SPP4		= !bK6_K6D_K64D;		//FPT4 input polarity selection						//├設定値とポート値が確実に整合するまで繰り返します。ポート値を取得して設定する間にポート値が変化した場合は繰り返します。エッジトリガなのでこうしないと割り込みが発生しなくなってしまう恐れがあります。
} while(bPINTPOL_SPPT_SPP4	       != !bK6_K6D_K64D);											//┘
do {																		//┐
	bPINTPOL_SPPT_SPP5		= !bK6_K6D_K65D;		//FPT5 input polarity selection						//├設定値とポート値が確実に整合するまで繰り返します。ポート値を取得して設定する間にポート値が変化した場合は繰り返します。エッジトリガなのでこうしないと割り込みが発生しなくなってしまう恐れがあります。
} while(bPINTPOL_SPPT_SPP5	       != !bK6_K6D_K65D);											//┘
	//--- 8-bit timer 0 ---
	//8-bit timer control register
	pT8_CTL0			= 3/*PTRUN|PSET*/;		//8-bit timer 0 clock control register (0x40160)
	precision_pad.chattering_wait	= ((PRECISION_PAD_CHATTERING_WAIT_MS * 48000) + ((256 * 256) - 1)) / (256 * 256);
	//                                                                     |||||    └─切り上げ用─┘    |||   +++- pT8_RLD0
	//                                                                     |||||                          +++------- bCLKCTL_T8_01_P8TS0
	//                                                                     +++++------------------------------------ θ/1000 (24MHz時はprecision_pad_callback()にて調整)
}
#else //PIECE
/*static*/ void  precision_pad_isr();		//IL=6,IE=0
/*static*/ void _precision_pad_isr();		//IL=6,IE=0
asm("
		.code
		.align		1
precision_pad_isr:
		call		_precision_pad_isr
		reti
		;//---------------------------------------------;//
_precision_pad_isr:
		pushn		%r2
  ;//{{2015/01/09変更:内蔵I/Oメモリのミラー空間を使って、_precision_pad_isr()の命令数を削減しました。
  ;//│* Fri Jan 09 21:26:07 JST 2015 Naoyuki Sawa
  ;//│- S1C33209/221/222テクニカルマニュアル『図4.1 メモリマップ』(s1c33209_221_222j.pdf B-II-4-4)参照
  ;//│  内蔵I/Oメモリの正式なアドレスは0x40000～0x4FFFFだが、0x30000～0x3FFFF,及び,0x50000～0x5FFFFにもミラーが見える。
  ;//│  内蔵I/Oメモリのミラー空間を使って命令数を削減する事が出来る。具体的には下記の通り:
  ;//│- 0x40000～0x4FFFFのベースアドレスをレジスタに保持してオフセットでアクセスする場合、即値0x40000をレジスタにロードするには3命令かかる。
  ;//│  しかし0x40000～0x4FFFFの代りに0x30000～0x3FFFFのミラー空間を使ってアクセスすれば、即値0x30000をレジスタにロードするには2命令で済む。
  ;//│- ベースアドレスに限らず、内蔵I/Oメモリの特定のアドレスをレジスタにロードする場合も、上記の工夫を適用可能である。
  ;//		xld.w		%r0, 0x40000			;//%r0  := plc_IO
  ;//↓2015/01/09変更:内蔵I/Oメモリのミラー空間を使って、_precision_pad_isr()の命令数を削減しました。
		xld.w		%r0, 0x30000			;//%r0  := plc_IO
  ;//}}2015/01/09変更:内蔵I/Oメモリのミラー空間を使って、_precision_pad_isr()の命令数を削減しました。
		xbset		[%r0+0x29f], 0			;//bRST_RESET_RSTONLY = 1
		ld.w		%r1, 0x0C			;//%r1  :=              0x0C
		xld.b		[%r0+0x287], %r1		;//pINT_FP47_FCT_FAD  = 0x0C
		sla		%r1, 2				;//%r1  :=              0x30
		xld.b		[%r0+0x280], %r1		;//pINT_FK01_FP03     = 0x30
		xbclr		[%r0+0x29f], 0			;//bRST_RESET_RSTONLY = 0
_precision_pad_isr_L10:						;//do {
		xld.ub		%r1, [%r0+0x2c1]		;//  %r1  :=                   pK5_K5D
		and		%r1, 0x18			;//  %r1  :=                  (pK5_K5D & 0x18)
		xld.b		[%r0+0x2cc], %r1		;//        pKINTCOMP_SCPK0  = (pK5_K5D & 0x18)
		xld.ub		%r2, [%r0+0x2c1]		;//  %r2  :=                   pK5_K5D
		and		%r2, 0x18			;//  %r2  :=                  (pK5_K5D & 0x18)
		cmp		%r1, %r2			;//} while(pKINTCOMP_SCPK0 != (pK5_K5D & 0x18))
		jrne		_precision_pad_isr_L10		;//
_precision_pad_isr_L20:						;//do {
		xld.ub		%r1, [%r0+0x2c4]		;//  %r1  :=                   pK6_K6D
		and		%r1, 0x0F			;//  %r1  :=                  (pK6_K6D & 0x0F)
		xld.b		[%r0+0x2cd], %r1		;//        pKINTCOMP_SCPK1  = (pK6_K6D & 0x0F)
		xld.ub		%r2, [%r0+0x2c4]		;//  %r2  :=                   pK6_K6D
		and		%r2, 0x0F			;//  %r2  :=                  (pK6_K6D & 0x0F)
		cmp		%r1, %r2			;//} while(pKINTCOMP_SCPK1 != (pK6_K6D & 0x0F))
		jrne		_precision_pad_isr_L20		;//
_precision_pad_isr_L30:						;//do {
		xld.ub		%r1, [%r0+0x2c4]		;//  %r1  :=                                             pK6_K6D
		not		%r1, %r1			;//  %r1  :=                                            ~pK6_K6D
		xand		%r1, %r1, 0x00000030		;//  %r1  :=                                           (~pK6_K6D & 0x30)
		xld.ub		%r2, [%r0+0x2c8]		;//  %r2  :=                  pPINTPOL_SPPT
		xand		%r2, %r2, 0xFFFFFFCF		;//  %r2  :=                 (pPINTPOL_SPPT & ~0x30)
		or		%r2, %r1			;//  %r2  :=                ((pPINTPOL_SPPT & ~0x30) | (~pK6_K6D & 0x30))
		xld.b		[%r0+0x2c8], %r2		;//        pPINTPOL_SPPT  = ((pPINTPOL_SPPT & ~0x30) | (~pK6_K6D & 0x30))
		xld.ub		%r2, [%r0+0x2c4]		;//  %r1  :=                                             pK6_K6D
		not		%r2, %r2			;//  %r2  :=                                            ~pK6_K6D
		xand		%r2, %r2, 0x00000030		;//  %r2  :=                                           (~pK6_K6D & 0x30)
		cmp		%r1, %r2			;//} while(pPINTPOL_SPPT != ((pPINTPOL_SPPT & ~0x30) | (~pK6_K6D & 0x30)))
		jrne		_precision_pad_isr_L30		;//
		ld.w		%r1, 3				;//pT8_CTL0 = 3
		xld.b		[%r0+0x160], %r1		;//
		ld.w		%r1, 8				;//precision_pad.chattering_wait = ((PRECISION_PAD_CHATTERING_WAIT_MS * 48000) + ((256 * 256) - 1)) / (256 * 256)
		xld.b		[precision_pad+6], %r1		;//
		popn		%r2
		ret
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
#ifndef PIECE
static void precision_pad_callback() {		//IL=6,IE=1
	if((char)(precision_pad.chattering_wait -= (bP0_P0D_P07D + 1)) > 0) { return; }
	//--- 8-bit timer 0 ---
	//8-bit timer control register
	pT8_CTL0			= 0;				//8-bit timer 0 clock control register (0x40160)
	precision_pad.chattering_wait	= 0;
	precision_pad_proc();
}
#else //PIECE
/*static*/ void precision_pad_callback();	//IL=6,IE=1
asm("
		.code
		.align		1
precision_pad_callback:
		xld.w		%r4, precision_pad+6		;//%r4  :=                  &precision_pad.chattering_wait
		xld.ub		%r6, [0x402d1]			;//%r6  :=                     pP0_P0D
		ld.ub		%r5, [%r4]			;//%r5  := chattering_wait = precision_pad.chattering_wait	*anti-interlock*
		sra		%r6, 7				;//%r6  :=                     bP0_P0D_P07D
		add		%r6, 1				;//%r6  :=                    (bP0_P0D_P07D + 1)
		sub		%r5, %r6			;//%r5  := chattering_wait -= (bP0_P0D_P07D + 1)
		jrule		3				;//if(chattering_wait > 0) {
		 ld.b		[%r4], %r5			;//  precision_pad.chattering_wait = chattering_wait
		 ret						;//  return }
		xld.b		[0x40160],%r8			;//pT8_CTL0 = 0
		ld.b		[%r4], %r8			;//precision_pad.chattering_wait = 0
		xjp		precision_pad_proc		;//goto precision_pad_proc
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
static void precision_pad_init() {
ENTER_CS;
	precision_pad.old_pcePadGetDirect = pceVectorSetKs(KSNO_PadGetDirect, new_pcePadGetDirect);
	pceVectorSetTrap(TRAP_K0, precision_pad_isr);			//Key input 0
	pceVectorSetTrap(TRAP_K1, precision_pad_isr);			//Key input 1
	pceVectorSetTrap(TRAP_P4, precision_pad_isr);			//Port input 4
	pceVectorSetTrap(TRAP_P5, precision_pad_isr);			//Port input 5
	//--- Port input interrupt ---
	//Key input interrupt select register
	bKINTSEL_SPPK_SPPK0		= 0/*K5[4:0]*/;			//FPK0 interrupt input port selection
	bKINTSEL_SPPK_SPPK1		= 0/*K6[3:0]*/;			//FPK1 interrupt input port selection
//┌─	//Key input interrupt input comparison register
//├─	pKINTCOMP_SCPK0			= 0x18/*K5[4:3]*/;		//Key input interrupt (FPK0) input comparison register (0x402cc)
//├─	pKINTCOMP_SCPK1			= 0x0F/*K6[3:0]*/;		//Key input interrupt (FPK1) input comparison register (0x402cd)
/*│*/	//Key input interrupt input mask register
/*│*/	pKINTMASK_SMPK0			= 0x18/*K5[4:0]*/;		//Key input interrupt (FPK0) input mask register (0x402ce)
/*│*/	pKINTMASK_SMPK1			= 0x0F/*K6[3:0]*/;		//Key input interrupt (FPK1) input mask register (0x402cf)
/*│*/	//Port input interrupt select register
/*│*/	bPINTSEL_SPT47_SPT4		= 0/*K64*/;			//FPT4 interrupt input port selection
/*│*/	bPINTSEL_SPT47_SPT5		= 0/*K65*/;			//FPT5 interrupt input port selection
//├─	//Port input interrupt input polarity select register
//├─	bPINTPOL_SPPT_SPP4		= 0/*low*/;			//FPT4 input polarity selection
//├─	bPINTPOL_SPPT_SPP5		= 0/*low*/;			//FPT5 input polarity selection
/*│*/	//Port input interrupt edge/level select register
/*│*/	bPINTEL_SEPT_SEPT4		= 1/*edge*/;			//FPT4 edge/level selection
/*│*/	bPINTEL_SEPT_SEPT5		= 1/*edge*/;			//FPT5 edge/level selection
/*│*/	//--- Interrupt controller ---
/*│*/	//Interrupt priority register
/*│*/	bINT_PK01L_PK0L			= 6;				//Key input 0 interrupt priority
/*│*/	bINT_PK01L_PK1L			= 6;				//Key input 1 interrupt priority
/*│*/	bINT_PP45L_PP4L			= 6;				//Port input 4 interrupt priority
/*│*/	bINT_PP45L_PP5L			= 6;				//Port input 5 interrupt priority
/*│*/	//Key input 0-1, port input 0-3 interrupt enable register
/*│*/	bINT_EK01_EP03_EK0		= 1;				//Key input 0
/*│*/	bINT_EK01_EP03_EK1		= 1;				//Key input 1
/*│*/	//Port 4-7, clock timer, A/D interrupt enable register
/*│*/	bINT_EP47_ECT_EAD_EP4		= 1;				//Port input 4
/*│*/	bINT_EP47_ECT_EAD_EP5		= 1;				//Port input 5
//├─	//Key input 0-1, port input 0-3 interrupt request flag register
//├─	//Port 4-7, clock timer, A/D interrupt request flag register
//├─	bRST_RESET_RSTONLY		= 1;				//Interrupt factor flag reset method selection
//├─	pINT_FK01_FP03			= 0x30;				//Key input 0, Key input 1
//├─	pINT_FP47_FCT_FAD		= 0x0C;				//Port input 4, Port input 5
//├─	bRST_RESET_RSTONLY		= 0;				//Interrupt factor flag reset method selection
/*│*/	//--- 8-bit timer 0 ---
/*│*/	//8-bit timer control register
/*│*/	pT8_CTL0			= 0;				//8-bit timer 0 clock control register (0x40160)
/*│*/	bCLKCTL_T8_01_P8TON0		= 0;				//8-bit timer 0 clock control
/*│*/	bCLKSEL_T8_P8TPCK0		= 0;				//8-bit timer 0 clock selection
/*│*/	bCLKCTL_T8_01_P8TS0		= CLKDIV_8T03_256;		//8-bit timer 0 clock division ratio selection
/*│*/	pT8_RLD0			= -1;				//8-bit timer 0 reload data register (0x40161)
/*│*/	bCLKCTL_T8_01_P8TON0		= 1;				//8-bit timer 0 clock control
//├─	pT8_CTL0 = 3/*PTRUN|PSET*/;					//8-bit timer 0 clock control register (0x40160)
/*│*/	install_8tu_interrupt_callback(0, precision_pad_callback);
/*└→*/_precision_pad_isr();
LEAVE_CS;
}
/*--------------------------------------------------------------------------*/
static void precision_pad_free() {
ENTER_CS;
	pceVectorSetKs(KSNO_PadGetDirect, precision_pad.old_pcePadGetDirect);
	//--- Interrupt controller ---
	//Key input 0-1, port input 0-3 interrupt enable register
	bINT_EK01_EP03_EK0		= 0;				//Key input 0
	bINT_EK01_EP03_EK1		= 0;				//Key input 1
	//Port 4-7, clock timer, A/D interrupt enable register
	bINT_EP47_ECT_EAD_EP4		= 0;				//Port input 4
	bINT_EP47_ECT_EAD_EP5		= 0;				//Port input 5
	//--- 8-bit timer 0 ---
	//8-bit timer control register
	pT8_CTL0			= 0;				//8-bit timer 0 clock control register (0x40160)
	bCLKCTL_T8_01_P8TON0		= 0;				//8-bit timer 0 clock control
	install_8tu_interrupt_callback(0, NULL);
LEAVE_CS;
}
#endif//USE_INPUT_INTERRUPT_PRECISION_PAD

/****************************************************************************
 *	アプリケーションパッドセンシング
 ****************************************************************************/

// * Mon Dec 31 16:36:08 JST 2007 Naoyuki Sawa
// - アプリケーションパッドセンシングを追加しました。
// - P/ECEの画面キャプチャソフトを使って画面を取り込んでいると、パッドの反応が悪くなることが有ります。
//   具体的には、ボタン押下のトリガを取りこぼすことがあります。
//   この不具合は、上で行った、高精度パッドセンシングの対策では解決できません。
// - この不具合の原因は、画面キャプチャのAPI(usr/PIECE/tools/isd/isdsub.cのismLCDCaptureScreen())が、
//   P/ECEアプリケーションを一時停止状態にすることにあります。
//   アプリケーションが一時停止中も、pcePadGetProc()は呼び出し続けられます。
//   pcePadGetProc()がトリガを検出したときに、たまたま、アプリケーションが一時停止状態で実行されず、
//   その後、再開すると、アプリケーションからはトリガが失われたように見えます。
//   詳しくは、usr/PIECE/sysdev/pcekn/runapp.cのAppLoop()を参照してください。
// - この問題を解決するため、トリガやオートリピートをアプリケーション側で管理する関数を追加しました。
//   今後、アプリケーションでパッドを読むときは、pcePadGet()の代わりにpad_get()を使ってください。
// - 高精度パッドセンシングの対策では、pcePadGetDirect()をフックして置き換える方法を採りましたが、
//   今回は、pcePadGet()をフックせずに、個別の関数としました。
//   そのようにした理由は、次のとおりです。
//   ① 今回追加したpad_get()、pad_set_repeat()は、既存のpcePadGet()、pcePadGetProc()、pcePadSetTrigMode()の
//      代わりに使うものですが、機能設計が異なっているので、フックして置き換えるのは妥当でないと考えました。
//   ② pcePadGetDirect()は、P/ECEカーネルもフック経由で利用しているので、フックする意味が有りましたが、
//      pcePadGet()、pcePadGetProc()、pcePadSetTrigMode()は、P/ECEカーネルは直接コールしているので、
//      フックしてもあまり意味がありません。
//   ③ pcePadGetDirect()は、既に、CLiPライブラリ内の様々な場所で利用されているので、そのままのスキームで
//      動作だけを改善する意味がありました。
//      pcePadGet()、pcePadGetProc()、pcePadSetTrigMode()は、ほぼ、アプリケーション内でしか使われないので、
//      今後のアプリケーションで新しい関数を使用するだけで、対応が容易だからです。
// - 今回のアプリケーションパッドセンシングの対策と、前回の高精度パッドセンシングの対策は、
//   それぞれ別の問題を改善するための対策です。
//   両方に意味がありますので、片方の対策を削除してしまったりしないよう、注意してください。

typedef struct _PADINF {
	unsigned char cnt;
	unsigned char delay;	/* リピート開始遅延 (0 = リピート無し) */
	unsigned char interval;	/* リピート中の間隔 (0 = リピート無し) */
} PADINF;
static PADINF padinf[8]; /*PAD_RI,PAD_LF,PAD_DN,PAD_UP,PAD_B,PAD_A,PAD_SELECT,PAD_START*/

int
pad_get()
{
	static int old;
	int pad;
	int trg;
	int i;
	int mask;
	PADINF* inf;

//{{2008/09/13:挙動改善のため変更
//	pad = pcePadGetDirect();
//↓2008/09/13:挙動改善のため変更
// * Sat Sep 13 15:22:47 JST 2008 Naoyuki Sawa
// - pad_get()の挙動を改善しました。
// - これまでは、一瞬だけボタンを押した場合などに、トリガを取りこぼすことかありました。
//   画面キャプチャの使用とは関係無く、特に、アプリケーションのFPSが低いとき(1FPS未満など)に顕著でした。
//   その原因は、ここでpcePadGetDirect()を呼んでしまっていたことでした。
//   高精度パッドセンシングによって置き換えたpcePadGetDirect()は、一瞬のボタン押下も取りこぼさないのですが、
//   システム内部でpcePadGetProc()経由で呼ばれるpcePadGetDirect()と、ここで呼んでいたpcePadGetDirect()の、
//   1フレームあたり二回呼んでしまって、一回目で取得した押下トリガを、二回目の呼び出しで失っていました。
//   pad_get()はpcePadGet()の代わりに使うのですから、リアルタイム入力のpcePadGetDirect()ではなく、
//   直前のpcePadGetProc()で保持された入力を返すpcePadGet()を用いるのが正解です。
//   pcePadGet()が返すトリガ成分は、上で説明したとおり不正確なので、pcePadGet()の押下成分だけを用います。
	pad = pcePadGet() & 255;
//}}2008/09/13:挙動改善のため変更
	trg = (old ^ pad) & pad;
	old = pad;

	inf = padinf;
	mask = 1;
	i = 8; /*PAD_RI,PAD_LF,PAD_DN,PAD_UP,PAD_B,PAD_A,PAD_SELECT,PAD_START*/
	do {
		if(trg & mask) {
			inf->cnt = inf->delay;
		} else if(pad & mask) {
			if(inf->cnt) { /* if(inf->delay && inf->interval) */
				if(!--inf->cnt) {
					trg |= mask;
					inf->cnt = inf->interval;
				}
			}
//{{2016/03/03削除:pad_get()内の無駄な処理を削除しました。これまで、'if(trg&mask){'が否定された時しか通らない処理の中で'trg&=~mask'を行っていました。'if(trg&mask){'が否定されたという事は必ず'!(trg&mask)'ですから、'trg&=~mask'を行う事は(害はないが)無駄でした。
//		} else {
//			trg &= ~mask;
//}}2016/03/03削除:pad_get()内の無駄な処理を削除しました。これまで、'if(trg&mask){'が否定された時しか通らない処理の中で'trg&=~mask'を行っていました。'if(trg&mask){'が否定されたという事は必ず'!(trg&mask)'ですから、'trg&=~mask'を行う事は(害はないが)無駄でした。
		}
		inf++;
		mask <<= 1;
	} while(--i);

	return pad | (trg << 8);
}

void
pad_set_repeat(int pad, int delay, int interval)
{
	int i;
	int mask;
	PADINF* inf;

	inf = padinf;
	mask = 1;
	i = 8; /*PAD_RI,PAD_LF,PAD_DN,PAD_UP,PAD_B,PAD_A,PAD_SELECT,PAD_START*/
	do {
		if(pad & mask) {
			inf->cnt = 0; /* 確実にリピート状態をリセット */
			inf->delay = delay;
			inf->interval = interval;
		}
		inf++;
		mask <<= 1;
	} while(--i);
}

/****************************************************************************
 *	main()を呼び出す前に初期化関数を実行する仕組み
 *	main()が終了した後に終了時関数を実行する仕組み
 ****************************************************************************/

// * Thu Jun 02 21:20:46 JST 2016 Naoyuki Sawa
// - 「main()を呼び出す前に初期化関数を実行する仕組み」を追加しました。
//   VC++の、同様の仕組みを参考にしました。
//   ただし、VC++では特定の名前を付けたデータセクションに初期化関数のポインタを登録するという方法ですが、P/ECE開発環境のgcc33.exeでは名前を付けてセクションを作成出来ないので、少し違う方法を取る事にしました。
//   CODEセクション内に初期化関数のポインタ配列を作成し、その直前に特定のシグネチャを置くという方法です。
//   ライブラリの初期化ルーチン(から呼び出される_cinit())が、CODEセクション全体を走査して、シグネチャを頼りに初期化関数のポインタ配列を見つけ、初期化関数を呼び出します。
//   シグネチャの文字列は何でも良かったのですが、VC++のセクション名に倣って".CRT$XIU"とする事にしました。
//   詳細は、clippce.c内のローカル関数_cinit()の実装を参照して下さい。
//   ちなみに、そのローカル関数_cinit()の名前も、VC++の同様の処理を行う内部関数_cinit()(C:\Program Files (x86)\Microsoft Visual Studio\VC98\CRT\SRC\CRT0DAT.C)から名前を取りました。
// - ■main()よりも前に呼び出される初期化関数を登録するには、以下のような配列を定義して下さい。
//   │static void InitFunc1() { ～ }      //初期化関数。関数名は任意です。
//   │static void InitFunc2() { ～ }      //初期化関数。関数名は任意です。
//   │static void* const TBL_InitTbl[]={  //初期化関数を登録する配列。配列名は任意です。
//   │    //       ~~~~~忘れないで!!
//   │    (void*)FOURCC_pCRT,             //┬配列のシグネチャ
//   │    (void*)FOURCC_dXIU,             //┘
//   │    InitFunc1,                      //┬0個以上、複数指定可
//   │    InitFunc2,                      //┘
//   │    NULL};                          //終端。忘れないで!!
//   上記の配列を、一つのモジュール内に複数定義する事も出来ますし、複数のモジュールに複数定義する事も出来ます。
//   また、ライブラリ内に定義する事も出来ますし、アプリケーション内に定義する事も出来ます。
//   上記の特性は、VC++の初期化関数の仕組みと同じです。
// * Fri Jun 03 21:47:06 JST 2016 Naoyuki Sawa
// - 「main()が終了した後に終了時関数を実行する仕組み」を追加しました。
//   方法は、昨日追加した「main()が終了した後に終了時関数を実行する仕組み」と同じです。
//   ".CRT$XTU",_cexit(),_initterm()の名前も、VC++の同様の処理を行う内部関数(C:\Program Files (x86)\Microsoft Visual Studio\VC98\CRT\SRC\CRT0DAT.C)から名前を取りました。
// - 終了時処理は既存のatexit()で登録出来るので、今回追加したような仕組みを追加する必要も無かったのですが、昨日追加した処理と_initterm()を共用して少ないコストで追加出来るので、追加しておく事にしました。
//   尚、atexit()で登録した関数と、今回追加した仕組みで登録した関数の順序は、atexit()で登録した関数の方が先です。(この点もVC++と同じです。)
// - ■main()が終了した後に呼び出される終了時関数を登録するには、以下のような配列を定義して下さい。
//   │static void ExitFunc1() { ～ }      //終了時関数。関数名は任意です。
//   │static void ExitFunc2() { ～ }      //終了時関数。関数名は任意です。
//   │static void* const TBL_ExitTbl[]={  //終了時関数を登録する配列。配列名は任意です。
//   │    //       ~~~~~忘れないで!!
//   │    (void*)FOURCC_pCRT,             //┬配列のシグネチャ
//   │    (void*)FOURCC_dXTU,             //┘
//   │    ExitFunc1,                      //┬0個以上、複数指定可
//   │    ExitFunc2,                      //┘
//   │    NULL};                          //終端。忘れないで!!
//   上記の配列を、一つのモジュール内に複数定義する事も出来ますし、複数のモジュールに複数定義する事も出来ます。
//   また、ライブラリ内に定義する事も出来ますし、アプリケーション内に定義する事も出来ます。
//   上記の特性は、VC++の終了時関数の仕組みと同じです。
#ifndef PIECE
static void _initterm(int sig) {
	extern const int __START_DEFAULT_CODE[], __END_DEFAULT_CODE[];				//CODEセクションの開始アドレス,終了アドレス
	const int* pos = __START_DEFAULT_CODE;							//CODEセクションの開始アドレスから…
	while(pos < (__END_DEFAULT_CODE - 1)) {							//CODEセクションの終了アドレスの2ワード前まで…			'～2ワード前まで'とした理由は、少なくとも".CRT"と"$X?U"の2ワードを取得出来なければ、これ以降に初期化セクションは存在し得ないからです。「pos<=(__END_DEFAULT_CODE-2)」でなく「pos<(__END_DEFAULT_CODE-1)」とした理由は、この後の処理で「(__END_DEFAULT_CODE-1)」の値を使うので、同じ値を使って不等号だけを変える方が、アセンブラ化した時にコードサイズを節約出来るからです。
		int c = *pos++;									//ワードを取得する。
		if(c == FOURCC_pCRT) {	//".CRT"						//".CRT"ならば…
			c = *pos++;								//次のワードを取得する。				①
			if(c == sig) {	//"$XIU" or "$XTU"					//"$XIU",又は,"$XTU"ならば…				②
				while(pos <= (__END_DEFAULT_CODE - 1)) {			//CODEセクションの終了アドレスの1ワード前まで…
					c = *pos++;						//次のワードを取得する。				③
					if((c & 1) ||						//┐
					   ((int*)c < __START_DEFAULT_CODE) ||			//├関数アドレスとして妥当な値でなければ、抜ける。		偶数アドレスで、CODEセクション内に有れば、関数アドレスとして妥当と見なす事にした。高速RAMに配置する関数は考慮しない。初期化関数を高速RAMに配置する事はまず無いと思うので、問題無いだろう。
					   ((int*)c > (__END_DEFAULT_CODE - 1))) { break; }	//┘							④	「c>(__END_DEFAULT_CODE-1)」とした理由は、関数のサイズは少なくとも1ワード(=2命令)以上だと思うからです。厳密には1命令(ret)の関数も有り得ますが、そのような初期化関数は実際には作らないですし、万一作ったとしてもCODEセクションの末尾に配置されるような偶然は起きないと思うので、これで大丈夫でしょう。「(__END_DEFAULT_CODE-1)」という共通の値を使い回したい意図も有ります。
					((void (*)())c)();					//関数を呼び出す。
				}
			}
			pos--;									//②がFalseだった場合は①で進み過ぎた分を戻す。④で抜けた場合は③で進み過ぎた分を戻す。
		}
	}
}
static void _cinit() {
	_initterm(FOURCC_dXIU);	//"$XIU"
}
static void _cexit() {
	_initterm(FOURCC_dXTU);	//"$XTU"
}
#else //PIECE
/*static*/ void _initterm(int sig);
/*static*/ void _cinit();
/*static*/ void _cexit();
asm("
		.code
		.align		1
_cinit:
		xld.w		%r12, 0x55495824			;//%r12 := '$XIU'
		jp		_initterm
		;//-----------------------------------------------------;//
_cexit:
		xld.w		%r12, 0x55545824			;//%r12 := '$XTU'
		;//-----------------------------------------------------;//
_initterm:
		pushn		%r3					;//
		ld.w		%r0, %r12				;//%r0  := sig = '$XIU' or '$XTU'
		xld.w		%r1, __START_DEFAULT_CODE		;//%r1  := pos = __START_DEFAULT_CODE
		ld.w		%r2, %r1				;//%r2  :=       __START_DEFAULT_CODE
		xld.w		%r3, __END_DEFAULT_CODE-4		;//%r3  :=       __END_DEFAULT_CODE - sizeof(int)
_initterm_LOOP1:							;//for(;;) {
		cmp		%r1, %r3				;//  if(pos >= (__END_DEFAULT_CODE - sizeof(int))) { return }
		jruge		_initterm_RET				;//  
		ld.w		%r4, [%r1]+				;//  %r4  := c = *pos++
		xcmp		%r4, 0x5452432E				;//  if(c != '.CRT') { continue }
		jrne		_initterm_LOOP1				;//  
		ld.w		%r4, [%r1]+				;//  %r4  := c = *pos++
		cmp		%r4, %r0				;//  if(c != sig) { goto REWIND }
		jrne		_initterm_REWIND			;//  
_initterm_LOOP2:							;//  for(;;) {
		cmp		%r1, %r3				;//    if(pos > (__END_DEFAULT_CODE - sizeof(int))) { goto REWIND }
		jrugt		_initterm_REWIND			;//    
	;//{{一命令節約
	;//	ld.w		%r4, [%r1]+				;//    %r4     := c = *pos++
	;//	xand		%r5, %r4, 1				;//    %psr(Z) := !(c & 1)
	;//↓一命令節約
		btst		[%r1], 0				;//    %psr(Z) := !(*pos & 1)
		ld.w		%r4, [%r1]+				;//    %r4     := c = *pos++
	;//}}一命令節約
		jrne		_initterm_REWIND			;//    if(!%psr(Z)) { goto REWIND }
		cmp		%r4, %r2				;//    if(c < __START_DEFAULT_CODE) { goto REWIND }
		jrult		_initterm_REWIND			;//    
		cmp		%r4, %r3				;//    if(c > (__END_DEFAULT_CODE - sizeof(int))) { goto REWIND }
		jrugt		_initterm_REWIND			;//    
		call		%r4					;//    (*c)()
		jp		_initterm_LOOP2				;//  }
_initterm_REWIND:							;//  
		jp.d		_initterm_LOOP1				;//}
		sub		%r1, 4					;//  %r1  := pos--	*delay*
_initterm_RET:								;//
		popn		%r3					;//
		ret							;//
");
#endif//PIECE

/*****************************************************************************
 *	P/ECE専用・擬似mainフレームワーク
 *****************************************************************************/

//{{2009/06/21:追加
int apptm_wk; /* 今回のpceAppProc()の開始時刻 */
//}}2009/06/21:追加

//{{2015/03/18変更:main()を呼び出す方法を変更しました。これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
//static int  app_init();
//↓2015/03/18変更:main()を呼び出す方法を変更しました。これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
static void app_init();
//}}2015/03/18変更:main()を呼び出す方法を変更しました。これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
static void app_proc();
static void app_exit();

#if 0 /*{{2004/12/27削除*/
//#if 0	/*{{このブロックは2004/09/16まで安定して動いていたので、念のため残してあります。*/
/////* * 汎用レジスタのうち、保存する必要があるのは、r0～r3のみです。
//// *   [S5U1C33000C_J.pdf p.84]
//// */
////typedef struct _CONTEXT {
////	int r0;		/* + 0,4 */
////	int r1;		/* + 4,4 */
////	int r2;		/* + 8,4 */
////	int r3;		/* +12,4 */
////	int* sp;	/* +16,4 */
////} CONTEXT;
/////* * system_contextとuser_contextは、static変数としてもいいのですが、
//// *   Cコンパイラは、asmブロック内で変数操作していることを判断できず、
//// *   "system_contextが定義されているが、使われていない"と警告します。
//// *   今回は、警告抑制のためだけに、グローバル変数とすることにします。
//// *→* Thu Sep 16 06:00:00 JST 2004 Naoyuki Sawa
//// *    「__attribute__((unused))」属性を指定することで、スタティック変数にできました。
//// *    この属性って、関数専用だと思い込んでいたのですけれど、変数にも指定できたのですね。
//// */
////static CONTEXT system_context __attribute__((unused)); /* システムコンテキスト保存用 */
////static CONTEXT user_context __attribute__((unused));   /* ユーザーコンテキスト保存用 */
//#else	/*}}このブロックは2004/09/16まで安定して動いていたので、念のため残してあります。*/
///* * Thu Sep 16 12:30:00 JST 2004 Naoyuki Sawa
// * - システム/ユーザー各コンテキストの%r0～%r3を、CONTEXT構造体に保存するのでなく、スタック上に保存するよう変更しました。
// *   システム/ユーザー各コンテキストについて、保存するのは%spだけになったので、CONTEXT構造体を廃止しました。
// * - 今回変更したような、レジスタをスタック上に保存する手法の方が、多くのマルチタスクシステムの一般的な手法です。
// */
//static int* system_sp __attribute__((unused)); /* システムスタックポインタ保存用 */
//static int* user_sp __attribute__((unused));   /* ユーザースタックポインタ保存用 */
//#endif
#endif /*}}2004/12/27削除*/
// * Mon Dec 27 12:13:00 JST 2004 Naoyuki Sawa
// - スタックレイアウトの変更に伴い、{{2004/12/27削除～}}のブロックは削除しました。
// - スタックポインタを保持する変数は、system_sp→ssp、user_sp→uspに変更しました。
//   これ以降のコードでsystem_sp、user_spを使っていた部分は、すべてssp、uspに置換しました。

//{{2014/12/24追加:「タイマ割り込み処理中の多重割り込み許可」を追加しました。
static void hook_unhook_timer();
//}}2014/12/24追加:「タイマ割り込み処理中の多重割り込み許可」を追加しました。

//{{2014/12/26追加:「8ビットプログラマブルタイマ割り込みの一元管理」を追加しました。
static void init_8tu_interrupt();
static void free_8tu_interrupt();
static void suspend_8tu_interrupt();
static void resume_8tu_interrupt();
//}}2014/12/26追加:「8ビットプログラマブルタイマ割り込みの一元管理」を追加しました。

//{{2009/03/30:PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチを適用
static void bugfix_pceHeapRealloc();
//}}2009/03/30:PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチを適用

//{{2014/12/07:pceHeapAlloc()の性能改善を行いました。
static void hook_unhook_first_fit_pceHeapAlloc();
//}}2014/12/07:pceHeapAlloc()の性能改善を行いました。

//{{2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
static void hook_unhook_die2();
//}}2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。

//{{2016/11/17追加:assert()マクロの実装を変更しました。(変更したファイルは、clippce.cとinclude/assert.hです。)
static void hook_unhook_assert();
//}}2016/11/17追加:assert()マクロの実装を変更しました。(変更したファイルは、clippce.cとinclude/assert.hです。)

void
pceAppInit()
{
	int i;

//{{2008/01/08:cstart.cのpceAppInit00()へ移動しました。
//	/*{{2007/01/08:高速除算ルーチン*/
//	{
//		/* 高速除算ルーチンをトラップテーブルの空き領域に転送します。
//		 * - この転送が完了するまで、整数除算が利用できません。
//		 *   他のあらゆる処理に優先して、できるだけ早い段階で実行する必要があります。
//		 * - app_init()で行っている、通常の高速RAMコード転送処理の段階では、遅すぎます。
//		 *   通常の高速RAMコード転送処理はエラー終了する可能性がありますが、高速除算ルーチンの初期化はエラーが許されません。
//		 *   高速除算ルーチンを、通常の高速RAM(FRAMC)ではなく、別の高速RAM領域(トラップベクタの空き領域)に分離した理由です。
//		 * - pceAppInit()よりも先に、pceAppInit00()から、memset()と、__version_check()が呼ばれています。
//		 *   幸い、これらの処理は整数除算を使わないので、大丈夫です。(__version_check()が失敗した場合も)
//		 */
//		void idiv_init(); /* clipidiv.s */
//		idiv_init();
//	}
//	/*{{2007/01/08:高速除算ルーチン*/
//}}2008/01/08:cstart.cのpceAppInit00()へ移動しました。

//{{2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
	//die2()のサポートルーチンを設定します。
	// - 詳細は、clippce.c内の「assert()マクロのサポートルーチン」の同日のコメントを参照して下さい。
	hook_unhook_die2();
//}}2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。

//{{2016/11/17追加:assert()マクロの実装を変更しました。(変更したファイルは、clippce.cとinclude/assert.hです。)
	//assert()マクロのサポートルーチンを設定します。
	// - この後の初期化処理の中でassert()が呼ばれる可能性が有るので、一番最初に実行しておく事にしました。
	//   実際には、2016/11/17現在は、CLiPライブラリ本体(clip1.lib,clip2.lib,fram.lib)のメイクファイル(/clip/Makefile)ではNDEBUGを未定義にしていないので、
	//   この後の初期化処理の中でassert()が呼ばれる事は無いのですが、もし今後、NDEBUGを未定義にした場合にも安全に動作するよう、こうしておく事にしました。
	hook_unhook_assert();
//}}2016/11/17追加:assert()マクロの実装を変更しました。(変更したファイルは、clippce.cとinclude/assert.hです。)

//{{2014/09/20追加:USB割込みとタイマ割込みの割込み優先度を下げました。
	/* USB割込みとタイマ割込みの割込み優先度を下げます。 */
	lower_restore_interrupt_priority(); /* 1回呼ぶと割込み優先度を下げる */
//}}2014/09/20追加:USB割込みとタイマ割込みの割込み優先度を下げました。

//{{2014/12/24追加:「タイマ割り込み処理中の多重割り込み許可」を追加しました。
	/* タイマ割り込み処理中の多重割り込みを許可します。 */
	hook_unhook_timer(); /* 1回呼ぶとフックする */
//}}2014/12/24追加:「タイマ割り込み処理中の多重割り込み許可」を追加しました。

//{{2014/12/26追加:「8ビットプログラマブルタイマ割り込みの一元管理」を追加しました。
	/* 8ビットプログラマブルタイマ割り込みの一元管理を開始します。 */
	init_8tu_interrupt();
//}}2014/12/26追加:「8ビットプログラマブルタイマ割り込みの一元管理」を追加しました。

//{{2009/03/30:PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチを適用
	/* pceHeapRealloc()バグ修正パッチを適用します。
	 * - PieceSystem1.20現在のpceHeapRealloc()にはバグがあるため、置き換えます。
	 *   もし今後、PieceSystemがバージョンアップし、pceHeapRealloc()のバグが修正されたら、bugfix_pceHeapRealloc()の呼び出しを削除してください。
	 *   ただし、その場合でも、置き換えを行なったままでも、本ライブラリ内のpceHeapRealloc()は正しい処理を行ないますので、悪影響は無いはずです。
	 * - このすぐ下で、ヒープマネージャAPIの排他制御を行なうためのフックを行なっているので、それよりも先に置き換え処理を行なうことが必要です。
	 *   もし逆にすると、置き換えたpceHeapRealloc()に対する排他制御が行なわれなくなってしまうので、注意してください。
	 */
	bugfix_pceHeapRealloc(); /* 1回呼ぶとフックする */
//}}2009/03/30:PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチを適用

//{{2014/12/07:pceHeapAlloc()の性能改善を行いました。
	/* pceHeapAlloc()の性能改善を適用します。
	 * - PieceSystem1.20現在のpceHeapAlloc()は遅いので、置き換えます。
	 *   もし今後、PieceSystemがバージョンアップし、pceHeapAlloc()の性能が改善されたら、hook_unhook_first_fit_pceHeapAlloc()の呼び出しを削除してください。
	 *   ただし、その場合でも、置き換えを行なったままでも、本ライブラリ内のpceHeapAlloc()は正しい処理を行ないますので、悪影響は無いはずです。
	 * - このすぐ下で、ヒープマネージャAPIの排他制御を行なうためのフックを行なっているので、それよりも先に置き換え処理を行なうことが必要です。
	 *   もし逆にすると、置き換えたpceHeapAlloc()に対する排他制御が行なわれなくなってしまうので、注意してください。
	 */
	hook_unhook_first_fit_pceHeapAlloc(); /* 1回呼ぶとフックする */
//}}2014/12/07:pceHeapAlloc()の性能改善を行いました。

	/* ヒープマネージャAPIの排他制御を設定します。 */
	hook_unhook_heapman(); /* 1回呼ぶとフックする */

	/* ファイル書き込み系関数の排他制御を設定します。 */
	hook_unhook_file(); /* 1回呼ぶとフックする */

	/* 4x6ドットフォントの半角カナ拡張を設定します。 */
	hook_unhook_font(); /* 1回呼ぶとフックする */

	/* 高精度パッドセンシングを開始します。 */
	precision_pad_init();

#if 0 /*{{2004/12/27削除*/
//	/* ユーザーコンテキストを初期化します。 */
//	/* * Fri Jun 21 19:22:00 JST 2002 Naoyuki Sawa
//	 * - これまで、システムスタックとユーザースタックのサイズの分配は、
//	 *       1000～1C00:ユーザースタック(3KB)
//	 *       1C00～2000:システムスタック(1KB)
//	 *   としていましたが、GSM再生機能の追加に伴い、システムスタックを増量しました。
//	 * - GSM再生ルーチンは、ユーザー／システムどちらのスタックも使う可能性があります。
//	 *   サウンド割り込みがユーザーコンテキストで発生した場合はユーザースタックを。
//	 *   サウンド割り込みがシステムコンテキストで発生した場合はシステムスタックを使います。
//	 * - GSM再生機能を使う場合、システムスタックが1C00～2000だけでは足りませんでした。
//	 *   実験したところ、必要ぎりぎりのシステムスタックサイズは1B00～2000のようです。
//	 * - 少し減らして1B80～にしてみると、START長押しのメニューから戻ったときに落ちます。
//	 *   P/ECE Watcherで観察すると、1B10まで使われていました。かなり危険です。
//	 * - そこで、少し余裕を持たせて1A00～2000までシステムスタックとすることにしました。
//	 *   これで、システムスタックには一応、256バイト程度の余裕があることになります。
//	 * - ユーザースタックにはまだ余裕があるようです。
//	 *   アプリケーションにもよりますが、自作ぬわんぎのミニゲームで試してみたところ、
//	 *   1800～1A00程度しか使われておらず、まだ2KB程度の余裕があるようです。
//	 * * Sun Nov 10 18:43:00 JST 2002 Naoyuki Sawa
//	 * - 参考までに、pceAppProc()が呼び出された時点での %sp 位置を調べてみました。
//	 *   BIOS 1.18で実験したところ、system_context.sp=0x1fe4 となりました。
//	 * * Sun Nov 10 19:01:00 JST 2002 Naoyuki Sawa
//	 * - これまでは、
//	 *	1000～1A00:ユーザースタック(2.5KB)
//	 *	1A00～2000:システムスタック(1.5KB)
//	 *   と、アドレス固定でシステムスタック／ユーザースタックを設定していましたが、
//	 *   今回より、pceAppInit()が呼ばれた時点の%spからの相対で設定するように変更しました。
//	 *   このように変更した理由は、スタックをSRAM上に取った場合にも対応できるようにするためです。
//	 * - スタックをSRAM上に取るには、リンカフラグ(LDFLAGS)に
//	 *	+defsym _stacklen=0x????	(0x????はスタックイサイズ[byte])
//	 *   を追加してください。例えばSRAM上に8KBのスタックを取るには、次のように指定します。
//	 *	+defsym _stacklen=0x2000
//	 *   _stacklen が定義されていると、スタックはBSS領域の直後、SRAM上に取られます。
//	 *   このとき、_stacklen の値は関係ありません。4KB未満でも、SRAM上に取られます。
//	 *   _stacklen を定義しないと、スタックは通常どおり高速RAMの後半4KB(0x1000～0x2000)に取られます。
//	 */
//#if 0	/*{{このブロックは2002/11/10まで安定して動いていたので、念のため残してあります。*/
////	user_context.sp = (int*)0x1A00;		/* 1000～1A00:ユーザースタック(2.5KB) */
////						/* 1A00～2000:システムスタック(1.5KB) */
//#elif 0	/*{{このブロックは2003/09/14まで安定して動いていたので、念のため残してあります。*/
////	asm("
////	xsub %r4, %sp, 0x0600			; user_context.sp = %sp - 1.5KB
////	xld.w [user_context+16], %r4		; 注意: 「xsub %r4, %sp, 0x0600÷4」としないように！
////						;       「sub %sp,imm」は「%sp<-%sp-imm×4」ですが、
////						;       「xsub %rd,%sp,imm」は「%rd<-%sp-imm」です！
////	");
//#elif 0	/*{{このブロックは2004/09/16まで安定して動いていたので、念のため残してあります。*/
////	/* * Sun Sep 14 21:44:00 JST 2003 Naoyuki Sawa
////	 * - 現在のスタックからシステムスタックを確保した残りをユーザースタックとして保存します。
////	 *   システムスタックサイズはpcc33.exeへのオプションで次のように定義してください。
////	 *	+defsym _sysstack=0x0600
////	 * - _sysstackを明示しない場合の既定値は、sysstack.sにて定義しています。
////	 *   2003/09/14現在のシステムスタックサイズ既定値は、これまでと同じく1.5KBです。
////	 * - システムスタックサイズ(_sysstack)が全スタックサイズ(_stacklen)を超えないよう注意してください。
////	 *   実行時には検査していません。
////	 * - システムスタックサイズを変更できるようにした理由は、割り込み中のスタックあふれを回避するためです。
////	 *   複雑なストリーム再生を行う場合など、システムコンテキストで割り込み中に割り込みがかかると、
////	 *   システムスタックがあふれてしまうことがあったからです。
////	 *   （実際には、MODやS3MやNSF等のストリーム再生では、既定値の1.5KBで充分でした。
////	 *     試験的に作ってみたOggVorbis(Tremor)のストリーム再生で、4～8KB(?)程度のシステムスタックが必要でした）
////	 */
////	asm("
////	ld.w %r4, %sp				; xsub %r4, %sp, _sysstack ではアセンブルエラーになります。
////	xld.w %r5, _sysstack			; （_sysstackがアセンブル時に確定していないから）
////	sub %r4, %r5				; 従って、このように分割しました。
////	xld.w [user_context+16], %r4
////	");
//#else	/*{{2004/09/16～現在の最新コードブロックです。*/
//	asm("
//	ld.w %r4, %sp				; xsub %r4, %sp, _sysstack ではアセンブルエラーになります。
//	xld.w %r5, _sysstack			; （_sysstackがアセンブル時に確定していないから）
//	sub %r4, %r5				; 従って、このように分割しました。
//	xld.w [usp], %r4
//	");
//#endif
//#if 0	/*{{このブロックは2004/09/16まで安定して動いていたので、念のため残してあります。*/
////	*--user_context.sp = (int)app_exit;	/* app_init()が制御を返した時に呼ばれる関数 */
////	*--user_context.sp = (int)app_init;	/* エントリーポイント(この中からmainを呼ぶ) */
//#else	/*{{2004/09/16～現在の最新コードブロックです。*/
//	*--usp = (int)app_exit;	/* app_init()が制御を返した時に呼ばれる関数 */
//	*--usp = (int)app_init;	/* エントリーポイント(この中からmainを呼ぶ) */
//	usp -= 4;		/* 初回app_proc()のpopn %r3に対応するダミー */
//#endif
#endif /*}}2004/12/27削除*/
// * Mon Dec 27 12:13:00 JST 2004 Naoyuki Sawa
// - スタックレイアウトの変更に伴い、{{2004/12/27削除～}}のブロックは削除しました。

	/* スタックレイアウトを行います。 */
	stack_layout();

	/* ユーザースタックを初期化します。 */
	stack_info.usp = stack_info.usrstack_end;
    //{{2015/03/18変更:main()を呼び出す方法を変更しました。これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
    //	*--stack_info.usp = (int)app_exit;	/* app_init()が制御を返した時に呼ばれる関数 */
    //	*--stack_info.usp = (int)app_init;	/* エントリーポイント(この中からmainを呼ぶ) */
    //↓2015/03/18変更:main()を呼び出す方法を変更しました。これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
	*--stack_info.usp = (int)app_exit;	/* main()が制御を返した時に呼ばれる関数 */
	*--stack_info.usp = (int)main;		/* app_init()が制御を返した時に呼ばれる関数 */
	*--stack_info.usp = (int)app_init;	/* エントリーポイント */
    //}}2015/03/18変更:main()を呼び出す方法を変更しました。これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
	stack_info.usp -= 4;			/* 初回app_proc()のpopn %r3に対応するダミー */

	///* スタックあふれ検査の準備。 */
	//stack_check_install();
	//→2004/12/29 stack_layout()で行うようにしました。

	///* 標準エラー出力を開きます。 */
	//stderr_open();
	//↓2003/11/22 初期状態では標準エラー出力なしに変更
	/* 初期状態では標準エラー出力なしとしておきます。
	 * 標準エラー出力を使うアプリケーションは、最初にstderr_open()を呼んでください。
	 * なお、ここで明示的にstderr_close()を呼んでいる理由は、
	 * 直前のアプリケーションがUSBCOM接続したまま終了してしまった場合への対策です。
	 */
	stderr_close();

	//{{2003/05/01:BIOS1.20で修正されました。もう不要です。
	///* 濃度のバラつきを防ぐため、LCDCを再初期化します。 */
	//lcd_init();
	//}}2003/05/01:BIOS1.20で修正されました。もう不要です。

	///* 高速版pceLCDDrawObjectをインストールします。 */
	//hook_LCDDrawObject();
	//↓Tue Jun 8 19:00:00 JST 2004 Naoyuki Sawa
	//カスタムpceLCDDrawObject()は4bit描画のためだけに使うことになったので、
	//高速切替・16階調表示使用時のみインストールするようにしました。

	/* 全サウンドチャネルのボリュームを最大にします。
	 * ※カーネルはアプリケーション起動時にボリュームを初期化していないので、
	 *   アプリケーションの中で初期化しないと前の設定を引き継いでしまいます！
	 */
	for(i = 0; i < 4; i++) pceWaveSetChAtt(i, 0/*減衰率0でボリューム最大*/);

    //{{2016/06/02追加:「main()を呼び出す前に初期化関数を実行する仕組み」を追加しました。
	//CODEセクション全体を走査して、シグネチャを頼りに初期化関数のポインタ配列を見つけ、初期化関数を呼び出します。
	_cinit();
    //}}2016/06/02追加:「main()を呼び出す前に初期化関数を実行する仕組み」を追加しました。
}

void
pceAppProc(int count)
{
//{{2009/06/21:追加
	/* 今回のpceAppProc()の開始時刻を記録します。 */
	apptm_wk = pceTimerGetCount();
//}}2009/06/21:追加

	/* yield()とスタックフレームの構成を合わせる必要があるので、
	 * 引数なしの関数を経由します。
	 */
	app_proc();

	/* スタックあふれ検査のを行います。
	 * スタックは 下位←ユーザースタック・システムスタック→上位 なので、（pceAppInit()参照）
	 * システムコンテキストよりもユーザーコンテキストがスタックの底を破壊する可能性が高いです。
	 * だから、ユーザーコンテキストから復帰した直後のここで検査を行うのがいいでしょう。
	 */
	stack_check();
}

void
pceAppExit()
{
	int i;

	/* atexit()で登録された関数があれば、呼び出します。 */
//{{2007/12/10:atexit()に複数の関数を登録できるよう変更しました。
//	if(exit_func != NULL) {
//		exit_func();
//	}
//↓2007/12/10:atexit()に複数の関数を登録できるよう変更しました。
//	/* atexit()で登録した順とは逆順に呼び出します。(ANSI仕様) */
//	while(exit_func[0]) {
//		(*exit_func[0])();
//		memmove(&exit_func[0],
//			&exit_func[1],
//			(sizeof exit_func[0]) * (ARRAY_SIZE(exit_func) - 1));
//		exit_func[ARRAY_SIZE(exit_func) - 1] = NULL;
//	}
//}}2007/12/10:atexit()に複数の関数を登録できるよう変更しました。
//↓2009/03/12:atexit()で登録した関数のコールバック中からも、さらにatexit()で関数を登録できるよう変更しました。
	/* atexit()で登録した順とは逆順に呼び出します。(ANSI仕様) */
	void (*func)();
	while((func = exit_func[0]) != NULL) {
		/* 関数をコールバックする前に、その関数の登録を解除しておきます。 */
		memmove(&exit_func[0],
			&exit_func[1],
			(sizeof exit_func[0]) * (ARRAY_SIZE(exit_func) - 1));
		exit_func[ARRAY_SIZE(exit_func) - 1] = NULL;
		/* 関数をコールバックします。 */
		(*func)();
	}

    //{{2016/06/03追加:「main()が終了した後に終了時関数を実行する仕組み」を追加しました。
	//CODEセクション全体を走査して、シグネチャを頼りに初期化関数のポインタ配列を見つけ、初期化関数を呼び出します。
	_cexit();
    //}}2016/06/03追加:「main()が終了した後に終了時関数を実行する仕組み」を追加しました。

	/* 確実にストリーム再生を停止します。 */
	stream_stop();

	/* 確実に赤外線通信を停止します。 */
	pceIRStop();

	/* 確実にサウンドを停止します。 */
	//pceWaveStop(0);
	//↓* Tue Jul 18 23:23:40 JST 2006 Naoyuki Sawa
	//↓- pceAppExit()にて、アプリケーション終了時のサウンド停止方法を、ソフト停止→ハード停止に変更しました。
	//↓  このように変更した理由は、以下のとおりです。
	//↓  アプリケーション終了時のサウンド停止方法にソフト停止を用いた場合、一定時間経過して自動的にハード停止となる前に、
	//↓  次のアプリケーションを起動すると、ノイズが鳴ることがあったからです。
	//↓  ノイズの原因は、恐らくアンプ回路などのアナログ的なものであり、カーネルやライブラリの不具合では無いと思いますが、
	//↓  やや耳障りであることは否めません。
	//↓  そこで、アプリケーション終了時のサウンド停止方法を、ソフト停止→ハード停止に変更しました。
	//↓  これまで、特別な理由があってソフト停止を選択していたわけではなく、今回の変更によるデメリットは何も無いはずです。
	pceWaveStop(1);

	/* 確実にタイマを停止します。 */
	for(i = 0; i < 4/*Undoc'dなCh.4は停止しません!(変更履歴を参照)*/; i++) {
		pceTimerSetCallback(i, PCE_TT_NONE, 0, NULL);
	}

	/* 高速版pceLCDDrawObjectをアンインストールします。 */
	//unhook_LCDDrawObject();
	//↓Tue Jun 8 19:00:00 JST 2004 Naoyuki Sawa
	//カスタムpceLCDDrawObject()は4bit描画のためだけに使うことになったので、
	//高速切替・16階調表示使用時のみインストールするようにしました。

//{{2015/03/14:pceAppExit()の中にfcloseall()の呼び出しを追加しました。
// * Sat Mar 14 21:29:01 JST 2015 Naoyuki Sawa
// - pceAppExit()の中にfcloseall()の呼び出しを追加しました。
//   fcloseall()の中でDIE()すると異常動作になるので注意してください。バグの検出ならば仕方が無いが起こり得るエラーに対してはfcloseall()はDIE()でなくエラー値を返すようにしてください。
//   pceAppExit()はfcloseall()が返したエラー値を無視して終了処理を継続します。
	/* 開いているすべてのストリームを閉じる。フラッシュされていないセクタバッファが残っていた場合、ここで書き出される。 */
	fcloseall();
//}}2015/03/14:pceAppExit()の中にfcloseall()の呼び出しを追加しました。

	/* 確実に標準エラー出力を閉じます。 */
	stderr_close();

	/* 確実に24MHzモードに戻します。 */
	turbo(0);

	/* 高精度パッドセンシングを終了します。 */
	precision_pad_free();

	/* 4x6ドットフォントの半角カナ拡張を解除します。 */
	hook_unhook_font(); /* 2回呼ぶとフック解除 */

	/* ファイル書き込み系関数の排他制御を解除します。 */
	hook_unhook_file(); /* 2回呼ぶとフック解除 */

	/* ヒープマネージャAPIの排他制御を解除します。 */
	hook_unhook_heapman(); /* 2回呼ぶとフック解除 */

//{{2014/12/07:pceHeapAlloc()の性能改善を行いました。
	/* pceHeapAlloc()の性能改善を解除します。 */
	hook_unhook_first_fit_pceHeapAlloc(); /* 2回呼ぶとフック解除 */
//}}2014/12/07:pceHeapAlloc()の性能改善を行いました。

//{{2009/03/30:PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチを適用
	/* pceHeapRealloc()バグ修正パッチを解除します。 */
	bugfix_pceHeapRealloc(); /* 2回呼ぶとフック解除 */
//}}2009/03/30:PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチを適用

//{{2014/12/26追加:「8ビットプログラマブルタイマ割り込みの一元管理」を追加しました。
	/* 8ビットプログラマブルタイマ割り込みの一元管理を終了します。 */
	free_8tu_interrupt();
//}}2014/12/26追加:「8ビットプログラマブルタイマ割り込みの一元管理」を追加しました。

//{{2014/12/24追加:「タイマ割り込み処理中の多重割り込み許可」を追加しました。
	/* タイマ割り込み処理中の多重割り込みを元に戻します。 */
	hook_unhook_timer(); /* 2回呼ぶとフック解除 */
//}}2014/12/24追加:「タイマ割り込み処理中の多重割り込み許可」を追加しました。

//{{2014/09/20追加:USB割込みとタイマ割込みの割込み優先度を下げました。
	/* USB割込みとタイマ割込みの割込み優先度を元に戻します。 */
	lower_restore_interrupt_priority(); /* 2回呼ぶと割込み優先度を元に戻す */
//}}2014/09/20追加:USB割込みとタイマ割込みの割込み優先度を下げました。

//{{2016/11/17追加:assert()マクロの実装を変更しました。(変更したファイルは、clippce.cとinclude/assert.hです。)
	//assert()マクロのサポートルーチンを解除します。
	hook_unhook_assert();
//}}2016/11/17追加:assert()マクロの実装を変更しました。(変更したファイルは、clippce.cとinclude/assert.hです。)

//{{2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
	//die2()のサポートルーチンを解除します。
	hook_unhook_die2();
//}}2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
}

//{{2014/12/26変更:「8ビットプログラマブルタイマ割り込みの一元管理」を追加しました。
//int pceAppNotify(int type, int param) {
//	extern unsigned char _def_vbuff[];
//	//
//	int retval;
//
//	/* atnotify()で登録された関数があれば、呼び出します。
//	 * 関数がAPPNR_IGNORE(=0)以外を返した場合は、その値をシステムへ返します。
//	 * 関数がAPPNR_IGNORE(=0)を返した場合は、規定の処理を行います。
//	 */
//	if(notify_func != NULL) {
//		retval = notify_func(type, param);
//		if(retval != APPNR_IGNORE) {
//			return retval;
//		}
//	}
//
//	/*{{規定の処理 C:/usr/PIECE/lib/src/defnotify.cより*/
//	switch(type) {
//	case APPNF_SMSTART:
//		return APPNR_ACCEPT;
//	case APPNF_SMREQVBUF:
//		pceLCDSetBuffer(_def_vbuff);
//		return APPNR_ACCEPT;
//	}
//	return APPNR_IGNORE;
//	/*}}規定の処理 C:/usr/PIECE/lib/src/defnotify.cより*/
//}
//↓2014/12/26変更:「8ビットプログラマブルタイマ割り込みの一元管理」を追加しました。
int pceAppNotify(int type, int param) {
	int ans = APPNR_IGNORE;
	//atnotify()で登録された関数が有れば、呼び出します。
	if(notify_func) {
		ans = (*notify_func)(type, param);
	}
	//atnotify()で登録された関数が無いか,又は,関数がAPPNR_IGNOREを返したら、既定の処理を行います。
	if(ans == APPNR_IGNORE) {
		//{{既定の処理(C:/usr/PIECE/lib/src/defnotify.cより)
		switch(type) {
		//システム・メニュー開始要求通知
		case APPNF_SMSTART:
			ans = APPNR_ACCEPT;
			break;
		//システム・メニュー画面バッファ要求通知
		case APPNF_SMREQVBUF:
			pceLCDSetBuffer(_def_vbuff);
			ans = APPNR_ACCEPT;
			break;
		}
		//}}既定の処理(C:/usr/PIECE/lib/src/defnotify.cより)
	}
	//通知タイプと、通知応答コードによって…
	switch(type) {
	//システム・メニュー開始要求通知
	case APPNF_SMSTART:
		//システムメニュー開始時に、
		//・高精度パッドセンシングを停止します。				この処理は必須ではないがスタンバイ開始時の処理に合わせることにした。この下の'スタンバイ要求通知'時の処理のコメントを参照せよ。
		//・8ビットプログラマブルタイマ割り込みをサスペンドします。
		if(ans == APPNR_ACCEPT) {	//SMSTARTの肯定応答はACCEPTのみです。
			precision_pad_free();
			suspend_8tu_interrupt();
		}
		break;
	//システム・メニュー復帰通知
	case APPNF_SMRESUME:
		//システムメニュー復帰時に、
		//・8ビットプログラマブルタイマ割り込みをレジュームします。
		//・高精度パッドセンシングを再開します。
		resume_8tu_interrupt();
		precision_pad_init();
		break;
	//スタンバイ要求通知
	case APPNF_STANDBY:
		//pceAppNotify()には、スタンバイ復帰通知が有りません。
		//その対策として、P/ECEカーネルに対してはスタンバイ要求を拒否し、自分自身でスタンバイします。
		//そうすることによって、スタンバイ復帰時のタイミングを知ることができます。
		if((ans == APPNR_ACCEPT) || (ans == APPNR_IGNORE)) {	//STANDBYの肯定応答はACCEPTとIGNOREです。
			//スタンバイ開始時に、
			//・高精度パッドセンシングを停止します。			入力割り込み版高精度パッドセンシングとP/ECEカーネルのスタンバイ復帰処理がぶつかるから。タイマ割り込み版高精度パッドセンシングの場合はこの処理は必須ではないがおこなっても害はない。
			//・8ビットプログラマブルタイマ割り込みをサスペンドします。
			//スタンバイ復帰時に、
			//・8ビットプログラマブルタイマ割り込みをレジュームします。
			//・高精度パッドセンシングを再開します。
			precision_pad_free();
			suspend_8tu_interrupt();
			pcePowerEnterStandby(0);
			resume_8tu_interrupt();
			precision_pad_init();
			ans = APPNR_REJECT;	//P/ECEカーネルに対してはスタンバイを拒否したことにします。
		}
		break;
	}
	return ans;
}
//}}2014/12/26変更:「8ビットプログラマブルタイマ割り込みの一元管理」を追加しました。

//{{2015/03/18変更:main()を呼び出す方法を変更しました。これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
//static int
//app_init()
//↓2015/03/18変更:main()を呼び出す方法を変更しました。これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
static void
app_init()
//}}2015/03/18変更:main()を呼び出す方法を変更しました。これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
{
	//int size;
	//→* Thu Jun 17 05:14:00 JST 2004 Naoyuki Sawa
	//→  メモリ管理を、EPSONライブラリmalloc()から、P/ECE API pceHeapAlloc()に変更しました。
	//→  変更に伴って、size変数は不要となったので、削除しました。

	/* * 失敗する可能性のある初期化処理は、pceAppInit()ではなく、ここで行ってください。
	 *   pceAppInit()の中で失敗を検出しても、アプリケーション終了の手段がないからです。
	 *   ※pceAppInit()の中でpceAppReqExit()を使っても効果がありません!! (BIOS1.18現在)
	 * * app_init()はpceAppProc()の中なので、pceAppReqExit()が有効です。
	 */

    //{{2014/12/23削除:高速RAMにコードを転送する処理を、clippce.cのapp_init()⇒cstart.cのpceAppInit00()へ移動しました。
    //	/* 高速RAMにコードを転送します。
    //	 * ※アプリケーションのリンク時にFRAMCのセクション定義を忘れると、ここでリンクエラーになります。
    //	 *   test.pceフォルダ内のMakefileにアプリケーション用Makefileのサンプルがありますので、
    //	 *   サンプルを参考にFRAMCのセクション定義を行ってください。
    //	 *   (test.pceフォルダをCLiP使用アプリケーションの雛型として使った場合は、既に定義されているはずです)
    //	 * * Tue Nov 19 12:30:00 JST 2002 Naoyuki Sawa
    //	 * - 高速RAM(0x1000～0x2000)をはみ出さないよう、範囲チェックを追加しました。
    //	 * * Tue Apr 1 19:00:00 JST 2003 Naoyuki Sawa
    //	 * - clipfram.c分割に伴い、参照するシンボル名を変更しました。
    //	 */
    //	if((int)__START_FRAM_ADDR != 0x1000) DIE();	/* 転送先アドレスが高速RAMの空きエリア先頭(0x1000)に一致すること */
    //	if((int)__SIZEOF_FRAM_ADDR > 0x1000) DIE();	/* 転送サイズが高速RAMの空きエリア(0x1000～0x2000)を超えないこと */
    //	memcpy(__START_FRAM_ADDR, __START_framtop_code, (int)__SIZEOF_FRAM_ADDR);
    //}}2014/12/23削除:高速RAMにコードを転送する処理を、clippce.cのapp_init()⇒cstart.cのpceAppInit00()へ移動しました。

	///* malloc()用ヒープ領域を初期化します。
	// * * 開放処理は行っていません。
	// *   アプリケーション終了後の、システムによるヒープリセットに任せています。
	// */
	//size = pceHeapGetMaxFreeSize();
	//size -= HEAPMARGIN;
	//if(size <= 0) DIE();    /* システムヒープも取れないほど、空きメモリが極端に少ない */
	//heap = pceHeapAlloc(size);
	//if(heap == NULL) DIE(); /* HEAPMARGIN＋数バイトの空きしかなく、管理領域が取れない */
	//memset(heap, 0x77, size); /* デバッグし易いように、適当な値でで埋めておきます */
	//ansi_InitMalloc((int)heap, (int)heap + size);
	//→* Thu Jun 17 05:14:00 JST 2004 Naoyuki Sawa
	//→  メモリ管理を、EPSONライブラリmalloc()から、P/ECE API pceHeapAlloc()に変更しました。
	//→  変更に伴って、EPSONライブラリmalloc()用ヒープの初期化を取りやめました。

    //{{2015/03/18削除:main()を呼び出す方法を変更しました。これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
    //	/* アプリケーション定義のエントリポイントへ。 */
    //	return main();
    //}}2015/03/18削除:main()を呼び出す方法を変更しました。これまではapp_init()から直接呼び出していましたが、今後はpceAppInit()でユーザースタックに積んで呼び出すようにしました。この変更によりmain()が呼び出された時のスタック消費量が4バイト少なくなりました。
}

static void
app_proc()
{
#if 0	/*{{このブロックは2004/09/16まで安定して動いていたので、念のため残してあります。*/
//	asm("
//	; システムコンテキストを退避します。
//	xld.w %r7, system_context
//	ld.w [%r7]+, %r0
//	ld.w [%r7]+, %r1
//	ld.w [%r7]+, %r2
//	ld.w [%r7]+, %r3
//	ld.w %r4, %sp
//	ld.w [%r7]+, %r4
//
//	; ユーザーコンテキストを復元します。
//	xld.w %r7, user_context
//	ld.w %r0, [%r7]+
//	ld.w %r1, [%r7]+
//	ld.w %r2, [%r7]+
//	ld.w %r3, [%r7]+
//	ld.w %r4, [%r7]+
//	ld.w %sp, %r4
//	");
#else	/*{{2004/09/16～現在の最新コードブロックです。*/
	/* * Thu Sep 16 12:30:00 JST 2004 Naoyuki Sawa
	 * - 一見、次のように書いた方が簡単ではないかと思うかも知れませんが、ダメです。
	 *	; システムコンテキストを退避します。
	 *	pushn %r3
	 *	xld.w [ssp], %sp
	 *	; ユーザーコンテキストを復元します。
	 *	xld.w %sp, [usp]
	 *	pushn %r3
	 * - 理由1:
	 *   命令エクステンダ(ext33.exe)は、"xld.w [ssp],%sp"は展開できますが、"xld.w %sp,[usp]"を展開できません。
	 *   シンタックスエラーになってしまいます。(%sp→メモリのストアは対応するが、%sp←メモリのロードには対応しない仕様)
	 * - 理由2:
	 *   命令エクステンダが"xld.w [ssp],%sp"を展開すると、冗長なコードになってしまいます。(マニュアル参照)
	 *   利用可能なワークレジスタ(今回は%r4を使用)があるならば、明示的に展開して書いた方が小さなコードになります。
	 * - app_exit(),yield()に関しても同様です。
	 */
	asm("
	; システムコンテキストを退避します。
	pushn %r3
	ld.w %r4, %sp
	xld.w [stack_info+24], %r4	; ssp

	; ユーザーコンテキストを復元します。
	xld.w %r4, [stack_info+28]	; usp
	ld.w %sp, %r4
	popn %r3
	");
#endif
}

static void
app_exit()
{
#if 0	/*{{このブロックは2004/09/16まで安定して動いていたので、念のため残してあります。*/
//	asm("
//	; システムコンテキストを復元します。
//	xld.w %r7, system_context
//	ld.w %r0, [%r7]+
//	ld.w %r1, [%r7]+
//	ld.w %r2, [%r7]+
//	ld.w %r3, [%r7]+
//	ld.w %r4, [%r7]+
//	ld.w %sp, %r4
//
//	; 終了要求を発行します。
//	ld.w %r12, %r10
//	xcall pceAppReqExit
//	");
#else	/*{{2004/09/16～現在の最新コードブロックです。*/
	asm("
	; システムコンテキストを復元します。
	xld.w %r4, [stack_info+24]	; ssp
	ld.w %sp, %r4
	popn %r3

	; 終了要求を発行します。
	ld.w %r12, %r10		; %r10には、main()からの戻り値が入っています。
	xcall pceAppReqExit
	");
#endif
}

void
yield()
{
#if 0	/*{{このブロックは2004/09/16まで安定して動いていたので、念のため残してあります。*/
//	asm("
//	; ユーザーコンテキストを退避します。
//	xld.w %r7, user_context
//	ld.w [%r7]+, %r0
//	ld.w [%r7]+, %r1
//	ld.w [%r7]+, %r2
//	ld.w [%r7]+, %r3
//	ld.w %r4, %sp
//	ld.w [%r7]+, %r4
//
//	; システムコンテキストを復元します。
//	xld.w %r7, system_context
//	ld.w %r0, [%r7]+
//	ld.w %r1, [%r7]+
//	ld.w %r2, [%r7]+
//	ld.w %r3, [%r7]+
//	ld.w %r4, [%r7]+
//	ld.w %sp, %r4
//	");
#else	/*{{2004/09/16～現在の最新コードブロックです。*/
	asm("
	; ユーザーコンテキストを退避します。
	pushn %r3
	ld.w %r4, %sp
	xld.w [stack_info+28], %r4	; usp

	; システムコンテキストを復元します。
	xld.w %r4, [stack_info+24]	; ssp
	ld.w %sp, %r4
	popn %r3
	");
#endif
}

int
atexit(void (*func)())
{
//{{2007/12/10:atexit()に複数の関数を登録できるよう変更しました。
//	/* 現在の実装では、二度目は失敗します。 */
//	//if(exit_func != NULL) return -1;
//	//↓2004/11/05 バグ発見しやすいよう、エラー終了としました。
//	if(exit_func != NULL) DIE();
//
//	/* 関数ポインタを格納します。 */
//	exit_func = func;
//↓2007/12/10:atexit()に複数の関数を登録できるよう変更しました。
	if(exit_func[ARRAY_SIZE(exit_func) - 1]) DIE();
	memmove(&exit_func[1],
		&exit_func[0],
		(sizeof exit_func[0]) * (ARRAY_SIZE(exit_func) - 1));
	exit_func[0] = func;
//}}2007/12/10:atexit()に複数の関数を登録できるよう変更しました。

	return 0;
}

int
atnotify(int (*func)(int type, int param))
{
	/* 現在の実装では、二度目は失敗します。 */
	//if(notify_func != NULL) return -1;
	//↓2004/11/05 バグ発見しやすいよう、エラー終了としました。
	if(notify_func != NULL) DIE();

	/* 関数ポインタを格納します。 */
	notify_func = func;

	return 0;
}

/****************************************************************************
 *	24MHz/48Mhzモード切り替え
 ****************************************************************************/

/* * 「P/ECE hacking」さん(http://junkbox.info/piece/)の
 *   『P/ECEを48MHzで動かす(1～3/3)』(http://junkbox.info/diary/dy200202.htm)を参考にさせて頂きました。
 * * SRAM/FLASHのオーバークロックは行っていません。
 *   今回の目標はあくまで各部品の仕様内で、CPUのみ48MHz動作させることだからです。
 * * 48MHz動作中に利用できないAPIは、次のとおりです。
 *	      pceCPUSetSpeed()			OSC3の倍速設定が想定されていないため
 *	      pcePowerEnterStandby()		 8bitタイマ1を設定しているため
 *	      pceTimerGetPrecisionCount()	後述
 *	      pceTimerAdjustPrecisionCount()	後述
 *	重要! pceFileCreate/Delete/WriteSec()	後述
 *	      pceIR系API全て			16bitタイマ5を設定しているため
 *   これらのAPIを利用する場合は、いったん24MHzモードに戻してから利用してください。
 * * pcePowerEnterStandby()は、アプリケーションから明示的に呼ばれる場合に加えて、
 *   電池駆動・五分間無操作時にシステムから自動的に呼ばれる場合もあります。
 *   本当は、 48MHzへの切り替え時に、pcePowerEnterStandby()だけはフックしておきたかったのですが、
 *   システム内からのpcePowerEnterStandby()呼び出しはフックを経由していないので、できませんでした。
 *   仕方がないので、48MHzモードを使うアプリケーションに次のようなコードを追加して対処してください。
 *	int pceAppNotify(int type, int param) {
 *		int m;
 *		switch(type) {
 *		case APPNF_SMSTART:	return APPNR_ACCEPT;
 *		case APPNF_SMREQVBUF:	pceLCDSetBuffer(_def_vbuff); return APPNR_ACCEPT;
 *		case APPNF_STANDBY:	m = turbo(0); pcePowerEnterStandby(0); turbo(m); return APPNR_REJECT;
 *		}
 *		return APPNR_IGNORE;
 *	}
 *   もっとも、pcePowerEnterStandby()内での8bitタイマ1利用目的は、
 *   スタンバイ復帰時のOSC3発振安定待ちのためであり、さほど重要ではありません。
 *   いや、重要といえば重要なのですが、待ち時間には充分なマージンが取られているはずなので、
 *   待ち時間が半分になった程度では即問題は発生しないと思うのです。
 *   もともと、初期のカーネルでは待ち時間の設定が正しく行われていなかった、にも関らず問題なく動いていたので、
 *   あまり気にしなくてもいいのかもしれません。
 *   というわけで、結論としては、アプリケーション側で上記コード(pceAppNotify)による対策を取ったほうが安全だが、
 *   対策しなくてもまあいいかな？、という結論になります。
 *   ちなみに、なぜCLiPライブラリ内に上記コード(pceAppNotify)を含めてしまわなかったかというと、
 *   pceAppNotify()はもっと多目的に利用されるので、アプリケーション側で定義できなくなってしまうのは問題だと思うからです。
 *   ↓2004/11/05追記
 *   pceAppNotify()をライブラリに含めました。
 *   アプリケーションがNotify処理を必要とする場合は、atnotify()を呼び出して関数を登録してください。
 * * 48MHzモードでは、P/ECEカーネル内のウェイト関数pdwait(\usr\PIECE\sysdev\pcekn\critical.c)のタイミングも
 *   変わってしまうのが心配ですが、いまのところとりあえず大丈夫みたいです。
 *   pdwait()のタイミングの大半は、CPU自身の命令実行時間ではなく、エリア11(0x1000000～)へのアクセス時間によって決定されます。
 *   エリア11のアクセスタイミングは、最低速に設定されています。(\usr\PIECE\sysdev\pcekn\hard.c 143行)
 *   外部バスへのアクセスなので、24MHz/48MHzの違いには影響されず、エリア11のアクセスはどちらでも同じです。
 *   エリア11のアクセス時間に較べれば、CPU自身の命令実行時間は小さいので、影響は小さいはずです。
 *   確かにpdwait()のタイミングは速くなってしまいますが、ぎりぎりの調整がなされていない限り問題ないと思います。
 *   唯一心配なのは、フラッシュメモリの消去待ちループ内での利用です。(\usr\PIECE\sysdev\pcekn\fmacc1.c 69行)
 *   pceFileCreate/Delete/WriteSec()利用時は、24MHzモードに戻しておいた方が安全でしょう。
 *   (※未検証です。もしかしたら48MHzでも問題なく使えるかもしれません)
 *   →ちょっと検証してみます。P/ECEのフラッシュメモリSST39VF400Aでは、1セクタ消去に要する最大時間は32[ミリ秒]です。
 *   \usr\PIECE\sysdev\pcekn\fmacc1.c 63～76行は、pdwait(5)×一万回の呼び出し以内に消去が完了しないと、タイムアウトします。
 *   簡単のために、mac命令五万回分の待ち時間と仮定します。(実際はもっとかかります)
 *   mac命令一回毎にエリア11から二回の読み出しを行うので、(1(基本リード)＋7(ウェイト))×2＝16クロックかかります。
 *   外部バスアクセスですので、ここでのクロック単位は、48MHzではなく24MHzです。
 *   1秒/24MHz×16クロック×五万回＝33ミリ秒となり、1セクタ消去に要する時間を満たしています。
 *   実際にはpdwait()の呼び出し時間やmac以外の命令実行時間も含まれるので、もっと多くのウェイトが取られているはずです。
 *   以上の検証より、48MHzモード中にpceFileCreate/Delete/WriteSec()を使ってもたぶん大丈夫だろうと思います。
 *   なお、フラッシュメモリへのワード書き込み(\usr\PIECE\sysdev\pcekn\fmacc2.c)は、
 *   タイムアウトなしで書き込み完了するまで待ち続けますので、pdwait()のタイミングには関係ありません。
 *   →と思ったのですが、実験してみると、48MHzモードではかなりの高確率でpceFileWriteSct()に失敗するようです。←★★★重要★★★
 *   一時的にturbo(0)で24MHzに戻し、直後にpceFileWriteCreate/Open/WriteSct/Close、そしてturbo(1)で48MHzを復元、
 *   とすれば、いまのところ失敗はありません。
 *   やっぱり、48MHzモードでのフラッシュメモリ書き込みはやめたほうがいいようです。
 *   →2002/12/10追記: 48MHzモードでpceFileCreate/WriteSecを使うとハングアップしていた理由がわかりました。
 *   フラッシュメモリへの書き込みが問題なのではなく、上記APIが内部でpceCPUSetSpeed()を呼び出しているのが原因でした。
 *   \usr\PIECE\sysdev\pcekn\file.c内のフラッシュメモリ消去/書き込み用補助関数の中でpceCPUSetSpeed(3)を呼び出し、
 *   いったん3MHzモードにしてフラッシュメモリ消去/書き込みを行っています。
 *   そして、pceCPUSetSpeed()によって、USBCエリアのアクセススピードが既定値に戻ってしまうのが、ハングアップの原因です。
 *   48MHzモードでpceFileCreate/WriteSecを使っても、USB通信が発生するまでは限りハングアップしないようですので、
 *   上記の推測はたぶん間違いないと思います。
 *   結論として、48MHzモードでpceFileCreate/WriteSecが使えないことには変わりありません。
 * * 16bitタイマ0(1msタイマ)の再設定について。
 *   他の16bitタイマ再設定と違って、これだけプリスケーラ分周比ではなくリロード値を再設定しています。
 *   このようにした理由は、P/ECEカーネル(BIOS 1.18)による分周比設定(=4)の倍(=8)の設定がないからです。
 *   止むを得ず、リロード値を倍(5999→11999)にしています。
 *   16bitタイマ0のカウント値は、実時間ベースで24MHz時の倍速で変化することになり、
 *   また、カウント値の最大値も24MHz時の倍の値までカウントアップされます。
 *   このため、pceTimerGetPrecisionCount()/pceTimerAdjustPrecisionCount()は正しい値を返しません。
 *   幸い、P/ECEカーネル内部では、16bitタイマ0のカウント値や上記APIは利用されていません。
 *   アプリケーション側でも、48MHz動作中は16bitタイマ0のカウント値や上記APIを利用しないようご注意ください。
 *   (pceTimerGetPrecisionCount()をフックして戻り値を半分にし、擬似的に24MHz互換の値を返すことは可能ですが、
 *    今回はそこまでやらないことにしました。)
 *   →2004/12/02追記: pceTimerGetPrecisionCount()はそのままで、pceTimerAdjustPrecisionCount()をTurboモード対応に置き換えました。
 *                     このモジュールの下の方にある、pceTimerAdjustPrecisionCount()の実装を参照してください。
 * * USBCのアクセスタイミング設定について。
 *   本来、48MHzモードにしても外部バスへのアクセスタイミングは変わらないので、
 *   USBCのアクセスタイミングを遅くする必要はないはずです。
 *   しかし実際には、現在のP/ECEカーネル(BIOS 1.18)では、USBCへのアクセスタイミングは、
 *   USBCの規定値よりも速く設定してあり、CPU側での処理時間も含めて要求タイミングを満たしています。
 *   (例えば通常の24MHzモードでも、連続してUSBCのポートを読み書きすると正しくアクセスできません。)
 *   48MHzモードではCPU側の処理時間が短くなるので、そのままでは要求タイミングを満たせず、
 *   USB通信ができなくなってしまいます。
 *   この問題を防ぐため、USBCのアクセスタイミングを遅くすることにしました。
 *   設定値は、S1C33209のBCUで可能な一番遅い設定にしてあります。(※設定値の検証はまだ行っていません)
 * * 8bitタイマ5(LCD転送)の再設定は行っていません。
 *   現在のP/ECEカーネル(BIOS 1.18)は、LCDCへのタイミングをかなり遅めに設定しているからです。
 *   P/ECEのLCDC、S6B0741のデータシート(66ページ)によると、SPI接続・2.7V以上で使った場合、
 *   S6B0741は約17MHzまでのSCLKについてくるようです。
 *   P/ECEカーネルは、LCDC転送速度を6MHzに設定しています。(\usr\PIECE\sysdev\pcekn\lcd.c 701～705行)
 *   倍速になっても12MHzですので、充分許容範囲です。
 *   それに、転送速度を遅くすると、pceLCDTrans()のライン追い越し問題発生する恐れもあります。(※実験まだです)
 *   以上の理由により、8bitタイマ5(LCD転送)の再設定は行わないことにしました。
 * * さて、24MHz→48MHzモードに切り替えることにより、どれぐらい速くなるか、です。
 *   コードを高速RAMに、データとスタックをSRAMに置いた場合を想定します。
 *   具体的には、高速RAM上に置いたnew_LCDDrawObject()の連続呼び出しで、24MHzと48MHz時の実行時間を比較しました。
 *   実験の結果、24MHz時に較べ、約20～30%程度の高速化となるようです。
 *   アプリケーション全体の実行時間の20～30%というと、MOD再生時間がまるまる儲かるぐらいの時間です。
 *   即ち、24MHzでBGMなしの場合と、48MHzでMODによるBGM再生を付けた場合の処理の重さがだいたい同じ、ということです。
 *   それなりに使いみちはありそうです。。
 */
static int
turbo_CLOCK(int mode)
{
	int old_mode = !bP0_P0D_P07D;		/* 直前のモードを保存(0<->1逆であることに注意!) */

	mode = mode != 0;			/* 新しいモードの指定を確実に0か1にする */
	if(old_mode == mode) return old_mode;	/* 変化なしなら何もしない */

	/* * 48MHzモードに切り替える前に、現在の設定がP/ECEカーネル(BIOS 1.18)の規定値かどうか検査しています。
	 *   P/ECEカーネルの規定値と異なっていた場合は、アサートします。
	 *   もしも今後、カーネルのバージョンアップによって規定値が変化した場合は、修正が必要です。
	 * * DI～EI内でDIE()した場合、エラー画面は表示されますが、SELECTによる終了はできません。
	 *   終了するには、背面のリセットボタンを押してください。
	 *   DIE()前にEIを発行して対処することは可能ですが、わざとやらないことにしました。
	 *   中途半端な設定状態メニューに戻ってしまうと非常に危険なので、
	 *   それよりもリセットボタンでの完全リセットを強制した方が安全だからです。
	 *   ↓2004/12/18 Naoyuki Sawa 変更
	 *   割り込み禁止/許可の制御は、呼び出し元のturbo()関数内で行うように変更しました。
	 */
	if(mode) {
		/* 16bitタイマ0(1msタイマ) */
		if(!bT16_CTL0_PRUN0) DIE();		bT16_CTL0_PRUN0 = 0;
		if(pT16_CR0B != 5999) DIE();		pT16_CR0B = 11999;
							bT16_CTL0_PRUN0 = 1; /* PRESETなしでも大丈夫かな？ */

		/* 16bitタイマ1(サウンド再生) */
		if(!bT16_CTL1_PRUN1) DIE();		bT16_CTL1_PRUN1 = 0;
		if(bCLKCTL_T16_1_P16TS1 != 0) DIE();	bCLKCTL_T16_1_P16TS1 = 1;
							bT16_CTL1_PRUN1 = 1; /* PRESETなしでも大丈夫かな？ */

		/* 16bitタイマ4(USB 6MHz) */
		if(!bT16_CTL4_PRUN4) DIE();		bT16_CTL4_PRUN4 = 0;
		if(bCLKCTL_T16_4_P16TS4 != 0) DIE();	bCLKCTL_T16_4_P16TS4 = 1;
							bT16_CTL4_PRUN4 = 1; /* PRESETなしでも大丈夫かな？ */

		/* USBコントローラのアクセスタイミング調整 */
		if(bA8_7_A8WT != 4) DIE();		bA8_7_A8WT = 7; /* 6でも一応動くようです */
		if(bA8_7_A8DF != 2) DIE();		bA8_7_A8DF = 3; /* 2でも一応動くようです */

		/* 入出力兼用ポート7(OSC3クロック制御) */
		if(bP0_P0D_P07D != 1) DIE();		bP0_P0D_P07D = 0;

	} else {
		/* 16bitタイマ0(1msタイマ) */
		if(!bT16_CTL0_PRUN0) DIE();		bT16_CTL0_PRUN0 = 0;
		if(pT16_CR0B != 11999) DIE();		pT16_CR0B = 5999;
							bT16_CTL0_PRUN0 = 1; /* PRESETなしでも大丈夫かな？ */

		/* 16bitタイマ1(サウンド再生) */
		if(!bT16_CTL1_PRUN1) DIE();		bT16_CTL1_PRUN1 = 0;
		if(bCLKCTL_T16_1_P16TS1 != 1) DIE();	bCLKCTL_T16_1_P16TS1 = 0;
							bT16_CTL1_PRUN1 = 1; /* PRESETなしでも大丈夫かな？ */

		/* 16bitタイマ4(USB 6MHz) */
		if(!bT16_CTL4_PRUN4) DIE();		bT16_CTL4_PRUN4 = 0;
		if(bCLKCTL_T16_4_P16TS4 != 1) DIE();	bCLKCTL_T16_4_P16TS4 = 0;
							bT16_CTL4_PRUN4 = 1; /* PRESETなしでも大丈夫かな？ */

		/* USBコントローラのアクセスタイミング調整 */
		if(bA8_7_A8WT != 7) DIE();		bA8_7_A8WT = 4;
		if(bA8_7_A8DF != 3) DIE();		bA8_7_A8DF = 2;

		/* 入出力兼用ポート7(OSC3クロック制御) */
		if(bP0_P0D_P07D != 0) DIE();		bP0_P0D_P07D = 1;
	}

	return old_mode;
}

static int
turbo_FLASH_WAIT(int mode)
{
	/* 現在のモードを取得します。 */
	int old_mode = bA10_9_A10WT == 1;	/* 1WAITならモード1、2WAITならモード0。ありえないが、1or2WAIT以外もモード0と見なす */

	/* 新しいモード指定を確実に0か1とします。 */
	mode = mode != 0;

	/* モード変化なしなら、何もしません。 */
	if(old_mode == mode) return old_mode;

	/* 新しいモードに応じたウェイトを設定します。 */
	bA10_9_A10WT = mode ? /*モード1*/1 : /*モード0*/2;

	return old_mode;
}

static int
turbo_SRAM_WAIT(int mode)
{
	/* 現在のモードを取得します。 */
	int old_mode = bA6_4_A5WT == 1;		/* 1WAITならモード1、2WAITならモード0。ありえないが、1or2WAIT以外もモード0と見なす */

	/* 新しいモード指定を確実に0か1とします。 */
	mode = mode != 0;

	/* モード変化なしなら、何もしません。 */
	if(old_mode == mode) return old_mode;

	/* 新しいモードに応じたウェイトを設定します。 */
	bA6_4_A5WT = mode ? /*モード1*/1 : /*モード0*/2;

	return old_mode;
}

static int
turbo_FLASH_OUTPUT_DISABLE(int mode)
{
	/* 現在のモードを取得します。 */
	int old_mode = bA10_9_A10DF == 0;	/* 0.5Cycleならモード1、1.5Cycleならモード0。ありえないが、0.5or1.5Cycle以外もモード0と見なす */

	/* 新しいモード指定を確実に0か1とします。 */
	mode = mode != 0;

	/* モード変化なしなら、何もしません。 */
	if(old_mode == mode) return old_mode;

	/* 新しいモードに応じた出力ディセーブル遅延時間を設定します。 */
	bA10_9_A10DF = mode ? /*モード1*/0 : /*モード0*/1;

	return old_mode;
}

static int
turbo_SRAM_OUTPUT_DISABLE(int mode)
{
	/* 現在のモードを取得します。 */
	int old_mode = bA6_4_A5DF == 0;		/* 0.5Cycleならモード1、1.5Cycleならモード0。ありえないが、0.5or1.5Cycle以外もモード0と見なす */

	/* 新しいモード指定を確実に0か1とします。 */
	mode = mode != 0;

	/* モード変化なしなら、何もしません。 */
	if(old_mode == mode) return old_mode;

	/* 新しいモードに応じた出力ディセーブル遅延時間を設定します。 */
	bA6_4_A5DF = mode ? /*モード1*/0 : /*モード0*/1;

	return old_mode;
}

int
turbo(int mode)
{
	int old_mode = 0;

ENTER_CS;
									/* ---cffss */
	old_mode |= turbo_CLOCK               ((mode >> 4) & 1) << 4;	/* ---c---- */
	old_mode |= turbo_FLASH_OUTPUT_DISABLE((mode >> 3) & 1) << 3;	/* ----f--- */
	old_mode |= turbo_FLASH_WAIT          ((mode >> 2) & 1) << 2;	/* -----f-- */
	old_mode |= turbo_SRAM_OUTPUT_DISABLE ((mode >> 1) & 1) << 1;	/* ------s- */
	old_mode |= turbo_SRAM_WAIT           ((mode >> 0) & 1) << 0;	/* -------s */

LEAVE_CS;

	return old_mode;
}

/*****************************************************************************
 *	P/ECEカーネルの不具合対応
 *****************************************************************************/

//{{2003/05/01:BIOS1.20で修正されました。もう不要です。
///* BIOS1.18のpceTimerAdjustPrecisionCount()には不具合があるので、正しい処理に置き換えます。
// * * BIOS1.18のpceTimerAdjustPrecisionCount()不具合について、掲示板より引用：
// *   | st=0x4e520808;ed=0xb02a085d;がこんな感じだったときに、
// *   | pceTimerAdjustPrecisionCount(st,ed)だと正の値ですが、
// *   | pceTimerAdjustPrecisionCount(ed,st)だとalが負になって返し値がまずくなるようです。
// *   | この現象はclockticksがまたぐときだけなので、
// *   | 計測中にまたいで、かつ65秒に１回だけ起こるようです。
// * * 高速版pceLCDDrawObject()のように、実行時にベクタをフックするのではなく、リンク時に置き換えます。
// *   高速版pceLCDDrawObject()は、未対応パラメータが指定された場合に本物のpceLCDDrawObject()を呼び出す必要があり、
// *   本物のpceLCDDrawObject()のアドレスも保存しておく必要があったので、実行時ベクタフックの方法を採っていました。
// *   これに対し、修正版pceTimerAdjustPrecisionCount()では、本物のpceTimerAdjustPrecisionCount()を呼び出す必要はなく、
// *   完全な置き換えですので、リンク時に置き換えてしまう方法を採りました。
// */
//unsigned long
//pceTimerAdjustPrecisionCount(unsigned long st, unsigned long ed)
//{
//#define TMCOUNTER 6000
//	int al = (ed & 0xffff) - (st & 0xffff);
//	//int ah =  (ed >> 16) - (st >> 16);		//BIOS1.18ではこうなっています
//	  int ah = ((ed >> 16) - (st >> 16)) & 0xffff;	//正しくはこう
//	if(al < 0) {
//		al += TMCOUNTER;
//		ah--;
//	}
//	al *= 1000;
//	al /= TMCOUNTER;
//	return ah * 1000 + al;
//#undef TMCOUNTER
//}
//}}2003/05/01:BIOS1.20で修正されました。もう不要です。

/* * Sat Dec 2 06:00:00 JST 2004 Naoyuki Sawa
 * - TurboON/OFF両モード対応のpceTimerAdjustPrecisionCount()を追加しました。
 *   pceTimerGetPrecisionCount()は、TurboONモードでも、P/ECEカーネル内のルーチンをそのまま利用して大丈夫です。
 * - 実行時にベクタをフックするのではなく、リンク時にpceapi.lib内のpceTimerAdjustPrecisionCount()を置き換えます。
 *   カーネル自身はpceTimerAdjustPrecisionCount()を呼び出していないので、ベクタをフックする必要はないからです。
 */
unsigned long
pceTimerAdjustPrecisionCount(unsigned long st, unsigned long ed)
{
	int tc = pT16_CR0B + 1;				/* 1[ms]未満の変化量の単位 = [(1/tc)ms] */
	int al = (ed & 0xffff) - (st & 0xffff);		/* 1[ms]未満の変化量 */
	int ah = ((ed >> 16) - (st >> 16)) & 0xffff;	/* 1[ms]単位の変化量 */

	/* (1[ms]未満の変化量)から(1[ms]単位の変化量)への繰り下がりを計算します。 */
	if(al < 0) {
		al += tc;
		ah--;
	}

	/* (1[ms]未満の変化量)の単位を[(1/tc)ms]→[us]に変換します。 */
	al = al * 1000 / tc;

	/* (1[ms]単位の変化量)の単位を[ms]→[us]に変換します。 */
	ah = ah * 1000;

	/* 1[us]単位の変化量を合計して返します。 */
	return ah + al;
}

//{{2015/01/07削除:「下位レベル関数/ダミー関数 : 標準Ｃライブラリ」の実装を、clippce.c⇒cliplibc.cへ移動しました。
///*****************************************************************************
// *	下位レベル関数/ダミー関数 : 標準Ｃライブラリ
// *****************************************************************************/
//
///* 下位レベル関数：	標準ライブラリ内部から呼ばれるもの。
// *			アプリケーションから直接使うことはありません。
// * ダミー関数：		標準ライブラリのヘッダで宣言されているが、
// *			実装されていない（ライブラリ内に存在しない）。
// * 従って、これらの関数を clippce.h で宣言する必要はありません。
// */
//
///* exit(),abort()から呼ばれます。 */
//void
//_exit()
//{
//	pceAppReqExit(0); /* 終了要求を発行して、 */
//	yield();          /* システムに制御を返す */
//	/* もう、yield()は制御を返しません。      */
//}
//
///* * EPSONライブラリの標準入出力用下位レベル関数です。
// *   EPSONライブラリのsscanf()を使うために定義しました。
// * - sscanf()はread()を使っていませんが、sscanf()が呼び出す_doscan()がscanf()と共用になっていて、
// *   _doscan()がscanf()から呼ばれた場合にread()を使っているため、read()のシンボル解決が要求されてしまいます。
// *   リンクエラーを回避するため、ダミーのread()関数を定義することにしました。
// * - sscanf()を使うだけならread()が呼ばれることはありません。
// */
//int
//read(int fd, char* buf, int nbytes)
//{
//	DIE();
//}
//}}2015/01/07削除:「下位レベル関数/ダミー関数 : 標準Ｃライブラリ」の実装を、clippce.c⇒cliplibc.cへ移動しました。

//{{2015/01/07削除:「EPSONライブラリの置き換え」の実装を、clippce.c⇒cliplibc.cへ移動しました。
///*****************************************************************************
// *	EPSONライブラリの置き換え
// *****************************************************************************/
//
///* * Thu Jun 17 05:14:00 JST 2004 Naoyuki Sawa 追記
// * - メモリ管理を、EPSONライブラリmalloc()から、P/ECE API pceHeapAlloc()に変更しました。
// *   本モジュールにてmalloc()/calloc()/realloc()/free()を再定義し、EPSONライブラリ内の同名関数をオーバーライドします。
// */
//void*
//malloc(size_t size)
//{
//	return pceHeapAlloc(size);
//}
//void
//free(void* ptr)
//{
//	/* pceHeapFree()はfree()と違ってNULLポインタを無視せず、開放しようとして失敗するので、まず明示的に検査します。
//	 * メモリブロックの開放に失敗した場合、たぶんアプリケーションプログラムのバグなので、DIE()することにしました。
//	 */
//	int retval;
//
//	if(ptr) {
//		retval = pceHeapFree(ptr);
//		if(retval != 0) DIE();
//	}
//}
//void*
//realloc(void* oldptr, size_t newsize)
//{
//	/* pceHeapRealloc()とrealloc()は、かなり仕様が異なります。
//	 * pceHeapRealloc()はメモリブロックを移動させません。
//	 * その場所のまま、サイズ拡張または縮小を行おうとします。(縮小は必ず成功するはずです)
//	 * realloc()の場合、その場所でサイズ拡張に失敗した場合、別のメモリブロックを確保して元の内容をコピーしますが、
//	 * pceHeapRealloc()はこの動作を行わず、ただちに失敗を返します。
//	 * →* Thu Jun 17 07:27:00 JST 2004 Naoyuki Sawa 追記
//	 * →  PieceSystem 1.20 現在のpceHeapRealloc()にはバグがあることがわかりました。
//	 * →  詳しくは、後述のコメントを参照してください。
//	 * →* Mon Mar 30 19:21:12 JST 2009 Naoyuki Sawa
//	 * →- PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチを適用しました。
//	 * →  詳しくは、後述のコメントを参照してください。
//	 */
//	int retval;
//	int oldsize;
//	void* newptr;
//	HEAPMEM* phm;
//
//	/* * realloc(NULL, newsize)は、malloc(newsize)と等価です。(realloc仕様)
//	 *   realloc(oldptr, 0)は、free(oldptr)と等価です。(realloc仕様)
//	 * * realloc(NULL, 0)の動作は、標準Cライブラリ仕様では未定義のようです。(※要確認)
//	 *   2004/06/17現在の実装は、{ free(NULL); return NULL; } の動作としてあります。
//	 */
//	if(!newsize) {
//		free(oldptr);
//		return NULL;
//	}
//	if(!oldptr) {
//		return malloc(newsize);
//	}
//
//	///* まず、その場でサイズ変更してみます。
//	// * 縮小の場合は、必ず成功するはずです。
//	// */
//	//retval = pceHeapRealloc(oldptr, newsize);
//	//if(retval == 0) return oldptr;
//	//→* Thu Jun 17 07:27:00 JST 2004 Naoyuki Sawa
//	//→  PieceSystem1.20現在のpceHeapRealloc()にはバグがあります。
//	//→  /usr/PIECE/system/pcekn/heapman.c 195行目の
//	//→	oldsize = (unsigned long)phm->chain - (unsigned long)phm;
//	//→  は間違いで、正しくは
//	//→	oldsize = (unsigned long)phm->chain - ((unsigned long)phm + sizeof(HEAPMEM));
//	//→  としなければいけないはずです。
//	//→  pceHeapRealloc()を呼ぶと、ヒープが破壊されたり、アドレス不整例外が発生したりします。
//	//→  仕方が無いので、常にメモリブロックの再割り当てを行うことにしました。
//	//→  サイズ縮小時は旧いメモリブロックをそのまま使うという手段もありますが、
//	//→  不要に大きなメモリブロックがいつまでも残ってしまうことを避けるために、
//	//→  常に要求されたサイズのメモリブロックを再割り当てすることにしました。
//	//----------------------------------------------------------------------------------------
//	// * Mon Mar 30 19:21:12 JST 2009 Naoyuki Sawa
//	// - PieceSystem1.20のpceHeapRealloc()バグに対する、上述の修正方法は誤りでした。
//	//   上述の修正方法では、元々不正だったHeapを増やすバグが治る代わりに、元々正しかったHeapを減らす処理が不正になってしまいます。
//	//   正しい修正方法は、/usr/PIECE/system/pcekn/heapman.cの195行目は変更せず、202行目と203行目の間に次の一行を追加することです。
//	//     if ( phm2->owner ) return 2; // 失敗(後ろに空きがない)
//	//     if(n < sizeof(HEAPMEM)) n = sizeof(HEAPMEM); // ←この一行を追加!!
//	//     n2 = (unsigned long)phm2->chain - (unsigned long)phm2;
//	// - PieceSystem1.20における、pceHeapRealloc()のバグを再現する方法は、以下のとおりです。
//	//     p = pceHeapAlloc(4);
//	//     pceHeapRealloc(p, 8); // ←ここでアドレス不正例外が発生します。
//	//   バグの原因は、204行目のAllocHEAPMEM()呼び出しにおいて、第二引数nに後続の空きブロックから取り込むバイト数を指定しています。
//	//   AllocHEAPMEM()の第二引数には、HEAPMEM構造体を含めた割り当てサイズを指定するのですが、上記のバグ再現コードのように、
//	//   4バイトだけ拡張する場合、HEAPMEM構造体のサイズ(8バイト)未満の新規メモリブロックを作成しようとしてしまいます。
//	//   AllocHEAPMEM()の中で、phm1の4バイト後にphm2を作成してしまい、phm1のchainフィールドを破壊してしまいます。
//	//   この問題を修正するには、拡張するバイト数がHEAPMEM構造体のサイズ(8バイト)未満の場合は、8バイトとすることです。
//	// - 以上の修正方法によって、pceHeapRealloc()が正しく動作するようになります。
//	//   なお、Heapを減らす処理(208行目)は元々正しいので、前述の修正方法ならばHeapを減らす処理には影響を与えません。
//	// - 以上を踏まえて、本ライブラリの初期化処理に、pceHeapRealloc()の修正パッチを組み込むことにしました。
//	//   具体的には、pceAppInit()とpceAppExit()に追加した、bugfix_pceHeapRealloc()呼び出しを参照してください。
//	//   pceHeapRealloc()修正パッチに伴い、上で"Thu Jun 17 07:27:00 JST 2004"にコメントアウトしていたコードを復元しました。
//	//   これまでは、realloc()は常にメモリを移動していましたが、今後は、pceHeapRealloc()が成功すればメモリを移動しなくなりました。
//	retval = pceHeapRealloc(oldptr, newsize);
//	if(retval == 0) return oldptr;
//
//	/* 新しいメモリブロックを確保します。
//	 * 新しいメモリブロックが確保できなかった場合は、元のメモリブロックを開放せずに、NULLを返します。(relloc仕様)
//	 */
//	newptr = malloc(newsize);
//	if(!newptr) return NULL;
//
//	/* 元のメモリブロックの内容を、新しいメモリブロックにコピーします。
//	 *
//	 *	|              |
//	 *	+--------------+ --- phm = oldptr - sizeof(HEAPMEM)
//	 *	|    HEAPMEM   |
//	 *	+--------------+ --- oldptr
//	 *	|              |  |
//	 *	| memory block |  |  oldsize = phm->chain - oldptr
//	 *	|              |  |
//	 *	+--------------+ --- phm->chain
//	 *	| next HEAPMEM |
//	 *	+--------------+
//	 *	|              |
//	 */
//	phm = (HEAPMEM*)((unsigned char*)oldptr - sizeof(HEAPMEM));
//	if(phm->mark != HEAPMEMMK) DIE();
//	oldsize = (unsigned char*)phm->chain - (unsigned char*)oldptr;
//	memcpy(newptr, oldptr, oldsize < newsize ? oldsize : newsize);
//
//	/* 元のメモリブロックを開放します。 */
//	free(oldptr);
//
//	return newptr;
//}
//
///* S1C33 Family Cコンパイラパッケージのcalloc()には問題があります。
// * calloc(～, 1)の形式で使ったときに、必要量の4倍ものメモリを確保しようとします。
// * おそらく、次のような実装になっているものと思われます。
// * 	void* calloc(size_t num, size_t size) {
// * 		int n;
// * 		void* p;
// * 		size = size + 3 & ~3;		// 要素サイズを4バイト単位に切り上げ
// * 		n = num * size;			// 割り当てバイト数を計算
// * 		p = malloc(n);			// 割り当てを行ってみる
// * 		if(p != NULL) memset(p, 0, n);	// 成功ならゼロクリア
// * 		return p;
// * 	}
// * これでは無駄が多すぎます。
// * 自前でcalloc()を用意し、ライブラリ内のcalloc()を置き換えることにします。
// * 参考文献：P.J.プラウガー著「標準Ｃライブラリ ANSI/ISO/JIS C規格」（トッパン）
// * 　　　　　初版 432ページ 図13.30 calloc.c
// *
// * * Thu Jun 17 05:14:00 JST 2004 Naoyuki Sawa 追記
// * - メモリ管理を、EPSONライブラリmalloc()から、P/ECE API pceHeapAlloc()に変更しました。
// *   EPSONライブラリ内のcalloc()をオーバーライドするために、引き続きここでcalloc()を定義しておきます。
// */
//void*
//calloc(size_t num, size_t size)
//{
//	size_t n = num * size;
//	void* p = malloc(n);
//	if(p) memset(p, 0, n);
//	return p;
//}
//
///* S1C33 Family Cコンパイラパッケージのstrtok()は完全にバグってます。
// * s1に与えた文字列の終端の'\0'を突き抜けて走査してしまいます。
// * 例えば次のコードを実行すると：
// *	char* s1 = "a/b/c\0d/e/f";
// *	char* p;
// *	p = strtok(s1, "/");
// *	while(p != NULL) {
// *		pceFontPrintf("(%s)", p);
// *		p = strtok(NULL, "/");
// *	}
// * 「(a)(b)(c)」ではなく、「(a)(b)(c)(d)(e)(f)」と表示されてしまいます。
// * 文字列s1が「c」の後の「\0」で終わっているのを認識できていません。
// * この例では「f」で止まっていますが、たまたま「f」の後ろに「\0\0...」と
// * ヌル文字が連続していただけで、そうでなっていなければ永遠に終わりません。
// * （S1C33のstrtok()は、「\0」が二個以上連続していないと終わらないようです。）
// * これはさすがに論外です。
// * 自前でstrtok()を用意し、ライブラリ内のstrtok()を置き換えることにします。
// * 参考文献：P.J.プラウガー著「標準Ｃライブラリ ANSI/ISO/JIS C規格」（トッパン）
// * 　　　　　初版 467ページ 図14.20 strtok.c
// */
//char*
//strtok(char* s1, const char* s2)
//{ /* find next token in s1[] delimited by s2[] */
//	static char* ssave = ""; /* for safety */
//	char *sbegin, *send;
//
//	sbegin = s1 ? s1 : ssave;
//	sbegin += strspn(sbegin, s2);
//	if(*sbegin == '\0') { /* end of scan */
//		ssave = ""; /* for safety */
//		return NULL;
//	}
//
//	send = sbegin + strcspn(sbegin, s2);
//	if(*send != '\0') *send++ = '\0';
//	ssave = send;
//	return sbegin;
//}
//
///* * Tue Jul 11 17:50:40 JST 2006 Naoyuki Sawa
// * - P/ECE開発環境付属のstrstr()には不具合があり、誤った結果を返します。
// *   不具合を再現するプログラム例を、以下に示します。
// *
// *	char dst1[] = { 'a', '\0', 'a', 'b', '\0' }; // 比較先文字列1
// *	char dst2[] = {       'a', 'a', 'b', '\0' }; // 比較先文字列2
// *	char src [] = {            'a', 'b', '\0' }; // 比較元文字列
// *	char* p1 = dstdst(dst1, "ab"); // NULLを返すべきだが、dst1[2]へのポインタを返してしまう
// *	char* p2 = dstdst(dst2, "ab"); // dst2[1]へのポインタを返すべきだが、NULLを返してしまう
// *
// *    誤りのパターンは二種類あります。以下、それぞれについて説明します。
// *
// *    1. マッチしてはいけないのにマッチしてしまう誤りの例:
// *
// *	dst1[]は、文字列として見ると、一文字目の"a"だけで終わっています。
// *	従って、
// *		strstr(dst1, src);
// *	は、
// *		strstr("a", "ab");
// *	と等価であり、結果はNULLになるはずです。
// *	しかし、P/ECE開発環境付属のstrstr()は誤って'\0'を突き抜け、
// *	三文字目～の"ab"にマッチして、三文字目の'a'へのポインタを返してしまいます。
// *
// *    2. マッチしなければいけないのにマッチしない誤りの例
// *
// *	dst2[]は、文字列として見ると、"aab"であり、二文字目～に"ab"を含んでいます。
// *	従って、
// *		strstr(dst2, src);
// *	は、二文字目の'a'へのポインタを返すはずです。
// *	しかし、P/ECE開発環境付属のstrstr()は誤ってNULLを返してしまいます。
// *	P/ECEのstrstr()は、まず、dst2[0～1]とsrc[0～1]の比較を行い、マッチしていないと判断した後、
// *	dst2[]側の走査位置をdst[1]に戻すのを忘れて、dst[2]から比較継続してしまっているみたいです。
// *
// * - P/ECE開発環境付属のstrstr()の不具合を回避するため、自前でstrstr()を用意しました。
// *   速度が要求される場面でstrstr()を使うことは無いと思うので、ごく単純な実装としました。
// *   ※要確認: strstr()の正式な仕様は、s1,s2が必ずヌル終端されていることを前提として良いのでしょうか?	┐
// *   もし、s1のどこかにs2が存在することが確実な場合に、s1がヌル終端されていないことを許す仕様ならば、	│2014/10/12追記
// *   以下の実装では正しく動作しません。(最初に行っているstrlen()が、どこまで行くか予測できないから。)	│strstr()がs1がヌル終端されていなくても動作するように修正しました。
// *   strstr()の正式な仕様を確認する必要があります。							┘
// */
////{{2014/10/12変更:strstr()がs1がヌル終端されていなくても動作するように修正しました。
////char*
////strstr(const char* s1, const char* s2)
////{
////	int n1 = strlen(s1);
////	int n2 = strlen(s2);
////
////	n1 -= n2;
////	while(n1 >= 0) {
////		if(!memcmp(s1, s2, n2)) {
////			return (char*)s1;
////		}
////		s1++;
////		n1--;
////	}
////	return NULL;
////}
////↓2014/10/12変更:strstr()がs1がヌル終端されていなくても動作するように修正しました。
///* * Sun Oct 12 18:49:43 JST 2014 Naoyuki Sawa
// * - strstr()がs1がヌル終端されていなくても動作するように修正しました。s2はヌル終端されていなければなりません。
// * - strcasestr()を追加しました。宣言はinclude/string.hに追加しました。strcasestr()はGNU拡張です。
// */
//#ifndef PIECE
//static char* strstr_sub(const char* s1, const char* s2, int (*fn)(const char*, const char*, size_t));
//char* strstr(const char* s1, const char* s2) {
//	return strstr_sub(s1, s2, strncmp);
//}
//char* strcasestr(const char* s1, const char* s2) {
//	return strstr_sub(s1, s2, strncasecmp);
//}
//static char* strstr_sub(const char* s1, const char* s2, int (*fn)(const char*, const char*, size_t)) {
//	/* 部分文字列の文字数を求める。(s2="")でも構わない。 */
//	int n = strlen(s2);
//	/* 文字列の先頭から検索を開始する。
//	 * - (s1="")の場合も、一回目のループは実行する。
//	 *   (s2="")の場合は、s1の内容にかかわらず((s1="")であってもなくても)常に一致するからです。 */
//	do {
//		/* 文字列の現在位置から、部分文字列の文字数分比較を行い、一致したら現在位置を返す。
//		 * - (s2="")だった場合、s1の内容にかかわらず((s1="")であってもなくても)、一致する。
//		 * - (s1="")だった場合、(s2="")でなければ、一致しない。 */
//		if(!(*fn)(s1, s2, n)) { return (char*)s1; }
//	/* 文字列の終端でなければ、位置を次へ進める。
//	 * - (s1="")だった場合は、一回目で抜けることになる。 */
//	} while(*s1++);
//	/* 文字列の終端における比較でも一致しなければ、NULLを返す。 */
//	return NULL;
//}
//#else /*PIECE*/
//char* strstr(const char* s1, const char* s2);
//char* strcasestr(const char* s1, const char* s2);
//asm("
//		.code
//		.align		1
//		.global		strstr
//		.global		strcasestr
//strstr:
//		xld.w		%r14, strncmp			;//%r14 := fn = strncmp
//		jp		strstr_sub
//strcasestr:
//		xld.w		%r14, strncasecmp		;//%r14 := fn = strncasecmp
//strstr_sub:
//		pushn		%r3
//		ld.w		%r0, %r12			;//%r0  := s1
//		ld.w		%r1, %r13			;//%r1  := s2
//		ld.w		%r2, %r14			;//%r2  := fn
//		xcall.d		strlen				;//%r10 := n = strlen(s2)
//		ld.w		%r12, %r1			;//%r12 :=            s2		*delay*
//		ld.w		%r3, %r10			;//%r3  := n
//strstr_sub_L10:							;//do {
//		ld.w		%r12, %r0			;//%r12 :=                s1
//		ld.w		%r13, %r1			;//%r13 :=                   s2
//		call.d		%r2				;//%r10 := retval = (*fn)(s1,s2,n)
//		ld.w		%r14, %r3			;//%r14 :=                      n	*delay*
//		cmp		%r10, 0				;//if(!retval) { return s1 }
//		jreq.d		strstr_sub_L20
//		ld.w		%r10, %r0			;//%r10 :=              s1		*delay*
//		ld.b		%r10, [%r0]+			;//%r10 := c = *s1++
//		cmp		%r10, 0				;//} while(c)
//		jrne		strstr_sub_L10
//strstr_sub_L20:
//		popn		%r3
//		ret
//");
//#endif/*PIECE*/
////}}2014/10/12変更:strstr()がs1がヌル終端されていなくても動作するように修正しました。
//
///* S1C33 Family C Compiler Package の pow() には不具合があり、間違った値を返すことがあります。
// * 例えば pow(1.25, 1.0) の結果が 0.0 になることがあります。まだ原因は究明できていません。
// * pow(1.0～1.5, 0.0以外) あたりが危ないようですが、微妙なコーディングの違いによって発症しないこともあります。
// * 現在、二種類の回避方法が発見されています。
// * ・pow()を呼ぶだけのラッピング関数pow_wrap()を用意し、常にpow_wrap()を経由してpow()を呼び出す。
// *   確かに回避できるのですが、なぜこれだけで回避できるのか全くわからないのが不安なところです。
// * ・pow()をexp()とlog()に展開し、pow()を使わないようにします。
// *   もしも不具合の原因がpow()単体の問題であったならば、この方法で確実に回避できるはずです。
// *   ただし、算術関数の呼び出しが増えるため、プログラムが遅くなってしまう欠点があります。
// * いずれにせよ、もともとの原因がわかっていないので、どちらの方法でも確実に回避できるとは言い切れません。
// */
////double
////pow(double x, double y)
////{
////	return exp(log(x) * y);
////}
////---------------------------------------------------------------------------
////↓* Sun Apr 22 21:28:18 JST 2007 Naoyuki Sawa
////↓- 上の実装では、xが負の場合に対応できていなかったので、以下のように修正しました。
////↓  以下の処理手順は、前述のように不具合のあるEPSONライブラリ版pow()とほぼ同じものですが、なぜか不具合を回避できるようです。
////↓  EPSONライブラリ版pow()の不具合は、処理手順そのものではなく、精度のために複雑な処理をしている部分に原因があるみたいです。
////↓- 不要なexp()やlog()が毎回リンクされてしまうのを避けるために、pow()を別モジュールに分離することも検討しましたが、
////↓  clipprnt.cがpow()を使っていて、結果的にほとんどのアプリケーションにリンク必要なので、分離する必要は無いと判断しました。
//double
//pow(double x, double y)
//{
//	double z;
//
//	if(x > 0.0) {
//		/* xが正ならば、そのまま計算します。
//		 */
//		z = exp(y * log(x));
//	} else if(x < 0.0) {
//		/* xが負ならば...
//		 * 1. yが整数でなければ、エラーです。(負のn乗根を含むため)
//		 * 2. xを正にして、計算します。
//		 * 3. yが奇数ならば、結果を負にします。
//		 */
//		if((int)y != y) {
//			DIE();
//		}
//		x = -x;
//		z = exp(y * log(x));
//		if((int)y & 1) {
//			z = -z;
//		}
//	} else { /* (x == 0.0) */
//		/* xが零ならば...
//		 * 1. yが正ならば、結果は0.0です。
//		 * 2. yが負ならば、エラーです。(ゼロ除算になるため)
//		 * 3. yが零ならば、結果は1.0です。
//		 */
//		if(y > 0.0) {
//			z = 0.0;
//		} else if(y < 0.0) {
//			DIE();
//		} else { /* (y == 0.0) */
//			z = 1.0;
//		}
//	}
//
//	return z;
//}
//
///* * Thu Oct 25 14:45:01 JST 2007 Naoyuki Sawa
// * - Aquaplus版よりも高速な、memcpy()を作成しました。
// * - P/ECE開発環境のmemcpy()は、EPSON版よりも高速な、Aquaplus版を使っています。
// *   今回、Aquaplus版を基にして、もっと高速で省サイズな、CLiP版を作成しました。
// * - コピーのループを工夫して、遅延スロットを最大限に活用しました。
// *   高速化の効果は、以下のとおりです。(TURBO=-1で計測)
// *	コピー処理 (×100回)               | Aquaplus版 | CLiP版    | 高速化
// *	-----------------------------------+------------+-----------+-------
// *	memcpy(0xc00000,0xd00000,0x100000) | 21140[ms]  | 18915[ms] | 約10%
// *	memcpy(0xc00002,0xd00002,0x100000) | 33379[ms]  | 28929[ms] | 約13%
// *	memcpy(0xc00001,0xd00001,0x100000) | 66757[ms]  | 57856[ms] | 約13%
// * - 終了判断を、memcpy_BYTEの一箇所にまとめて、コードサイズを削減しました。
// *   コードサイズ削減の効果は、以下のとおりです。
// *	Aquaplus版=68[byte] → CLiP版=64[byte]
// * * Tue Jan 08 14:58:19 JST 2008 Naoyuki Sawa
// * - memcpy()を、ベクタ領域のFRAMに転送して実行するために、clipidiv.sへ移動しました。
// *   ベクタ領域に収まるように、並べ替えを行いましたが、コード内容は変更していません。
// */
////{{2008/01/08:削除(clipidiv.sへ移動)
////asm("
////	.code
////	.align	1
////	.global	memcpy
////memcpy:
////	;// %r12 := dst
////	;// %r13 := src
////	;// %r14 := cnt
////	cmp	%r14, 3		; if(cnt<=3) memcpy_BYTE
////	jrule.d	memcpy_BYTE
////	ld.w	%r10, %r12	; %r10 := dst (戻り値)			*delay*
////	ld.w	%r9, %r12	; %r9  := dst
////	or	%r9, %r13	; %r9  := dst|src
////	and	%r9, 3		; %r9  := (dst|src)&3
////	jreq	memcpy_WORD	; if(!((dst|src)&3)) memcpy_WORD
////	and	%r9, 1		; %r9  := (dst|src)&1
////	jreq	memcpy_HALF	; if(!((dst|src)&1)) memcpy_HALF
////	;-----------------------;
////memcpy_BYTE:
////	sub	%r14, 1		; %r14 := (cnt-1) ------+
////	jrult	memcpy_RET	; if((cnt-1)<0) return  |
////	 ld.b	%r9, [%r13]+	;   byte = *src++       |
////	 ld.b	[%r12]+, %r9	;   *dst++ = byte       |
////	jrugt.d	-2		; while((cnt-1)>0) <----+ フラグ影響
////	sub	%r14, 1		; %r14 := (cnt-1) -= 1 -+		*delay*
////memcpy_RET:
////	ret
////	;-----------------------;
////memcpy_HALF:
////	xsub	%r15, %r14, 3	; %r15 := (cnt-3) ------+
////	 ld.h	%r9, [%r13]+	;   half = *src++       |
////	 ld.h	[%r12]+, %r9	;   *dst++ = half       |
////	jrugt.d	-2		; while((cnt-3)>0) <----+ フラグ影響
////	sub	%r15, 2		; %r15 := (cnt-3) -= 2 -+		*delay*
////	jp.d	memcpy_BYTE
////	and	%r14, 1		; %r14 := cnt &= 1			*delay*
////	;-----------------------;
////memcpy_WORD:
////	xsub	%r15, %r14, 7	; %r15 := (cnt-7) ------+
////	 ld.w	%r9, [%r13]+	;   word = *src++       |
////	 ld.w	[%r12]+, %r9	;   *dst++ = word       |
////	jrugt.d	-2		; while((cnt-7)>0) <----+ フラグ影響
////	sub	%r15, 4		; %r15 := (cnt-7) -= 4 -+		*delay*
////	jp.d	memcpy_BYTE
////	and	%r14, 3		; %r14 := cnt &= 3			*delay*
////");
////}}2008/01/08:削除(clipidiv.sへ移動)
//
///* * Thu Oct 25 14:45:01 JST 2007 Naoyuki Sawa
// * - Aquaplus版よりも高速な、memset()を作成しました。
// * - P/ECE開発環境のmemset()は、EPSON版よりも高速な、Aquaplus版を使っています。
// *   今回、Aquaplus版を基にして、もっと高速で省サイズな、CLiP版を作成しました。
// * - フィルのループを工夫して、遅延スロットを最大限に活用しました。
// *   高速化の効果は、以下のとおりです。(TURBO=-1で計測)
// *	フィル処理 (×100回)        | Aquaplus版 | CLiP版    | 高速化
// *	----------------------------+------------+-----------+-------
// *	memset(0xc00000,0,0x100000) | 13352[ms]  | 11127[ms] | 約16%
// *	memset(0xc00002,0,0x100000) | 22252[ms]  | 17802[ms] | 約20%
// *	memset(0xc00001,0,0x100000) | 44505[ms]  | 35604[ms] | 約20%
// * - memset_WORDとmemset_HALFのための、フィル値拡張処理を一部共通化、および、
// *   終了判断を、memset_BYTEの一箇所にまとめて、コードサイズを削減しました。
// *   コードサイズ削減の効果は、以下のとおりです。
// *	Aquaplus版=80[byte] → CLiP版=68[byte]
// * * Tue Jan 08 14:58:19 JST 2008 Naoyuki Sawa
// * - memset()を、ベクタ領域のFRAMに転送して実行するために、clipidiv.sへ移動しました。
// *   ベクタ領域に収まるように、並べ替えを行いましたが、コード内容は変更していません。
// */
////{{2008/01/08:削除(clipidiv.sへ移動)
////asm("
////	.code
////	.align	1
////	.global	memset
////memset:
////	;// %r12 := dst
////	;// %r13 := v,?,?,?
////	;// %r14 := cnt
////	cmp	%r14, 3		; if(cnt<=3) memset_BYTE
////	jrule.d	memset_BYTE
////	ld.w	%r10, %r12	; %r10 := dst (戻り値)			*delay*
////	xand	%r9, %r12, 1	; %r9  := dst&1
////	jrne	memset_BYTE	; if(dst&1) memset_BYTE
////	ld.ub	%r15, %r13	; %r15 := v,0,0,0
////	sll	%r13, 8		; %r13 := 0,v,?,?
////;//	or	%r13, %r15	; %r13 := v,v,?,?-------+
////	xand	%r9, %r12, 2	; %r9  := dst&2         |
////	jrne.d	memset_HALF	; if(dst&2) memset_HALF |
////	or	%r13, %r15	; <---------------------+		*delay*
////	ld.uh	%r13, %r13	; %r13 := v,v,0,0
////	swap	%r15, %r13	; %r15 := 0,0,v,v
////	or	%r13, %r15	; %r13 := v,v,v,v
////	;-----------------------;
////memset_WORD:
////	xsub	%r15, %r14, 7	; %r15 := (cnt-7) ------+
////	 ld.w	[%r12]+, %r13	;   *dst++ = v,v,v,v    |
////	jrugt.d	-1		; while((cnt-7)>0) <----+ フラグ影響
////	sub	%r15, 4		; %r15 := (cnt-7) -= 4 -+		*delay*
////	jp.d	memset_BYTE
////	and	%r14, 3		; %r14 := cnt &= 3			*delay*
////	;-----------------------;
////memset_HALF:
////	xsub	%r15, %r14, 3	; %r15 := (cnt-3) ------+
////	 ld.h	[%r12]+, %r13	;   *dst++ = v,v        |
////	jrugt.d	-1		; while((cnt-3)>0) <----+ フラグ影響
////	sub	%r15, 2		; %r15 := (cnt-3) -= 2 -+		*delay*
////	and	%r14, 1		; %r14 := cnt &= 1
////	;-----------------------;
////memset_BYTE:
////	sub	%r14, 1		; %r14 := (cnt-1) ------+
////	jrult	memset_RET	; if((cnt-1)<0) return  |
////	 ld.b	[%r12]+, %r13	;   *dst++ = v          |
////	jrugt.d	-1		; while((cnt-1)>0) <----+ フラグ影響
////	sub	%r14, 1		; %r14 := (cnt-1) -= 1 -+		*delay*
////memset_RET:
////	ret
////");
////}}2008/01/08:削除(clipidiv.sへ移動)
//
//;// * Sun Mar 11 22:22:46 JST 2012 Naoyuki Sawa
//;// - EPSONライブラリのmemmoveを置き換えました。
//;// - EPSONライブラリのmemmoveも正しく動作はしますが、以下の問題があります。
//;//   ・memcpyを利用できる場合は、memcpyを利用する方が効率が良い。
//;//	memcpyの方が、ワード単位コピーで最適化されており、高速だからである。
//;//	しかし、EPSONライブラリのmemmoveは、内部でバイト単位コピーを行ってしまい、低速である。
//;//   ・前方コピーの場合は、常にmemcpyを利用できる。
//;//	従って、memmove内部に、前方コピーの実装を含める必要は無い。
//;//	しかし、EPSONライブラリのmemmoveは、前方コピーを含んでしまっており、容量の無駄である。
//;//   ・EPSONライブラリのmemmoveは、後方コピーのループ終端判断を、ループの先頭で行っています。
//;//	ループ終端判断を、ループの末尾で行う方が、高速化できます。
//;// - 今回は、上記三点を改善した、memmoveを実装しました。
//;//
//;//-----------------------------------------------------------------------------------------------------------------------------
//;//
//;//	((dest <= src) || (dest >= src+count)) が成立する場合、memcpyを利用できる
//;//
//;//		○memcpyを利用できる
//;//
//;//			dest
//;//			├───────────┐
//;//			└───────────┘	┌───────────┐
//;//							├───────────┤
//;//							src			src+count
//;//
//;//		○memcpyを利用できる
//;//
//;//					dest
//;//					├───────────┐
//;//					└───────┬───┴───────┐
//;//							├───────────┤
//;//							src			src+count
//;//
//;//		○memcpyを利用できる
//;//
//;//							dest			
//;//							├───────────┐
//;//							├───────────┤
//;//							├───────────┤
//;//							src			src+count
//;//
//;//		×memcpyを利用できない
//;//
//;//									dest
//;//									├───────────┐
//;//							┌───────┴───┬───────┘
//;//							├───────────┤
//;//							src			src+count
//;//
//;//		○memcpyを利用できる
//;//
//;//										dest
//;//										├───────────┐
//;//							┌───────────┼───────────┘
//;//							├───────────┤
//;//							src			src+count
//;//
//;//		○memcpyを利用できる
//;//
//;//											dest
//;//											├───────────┐
//;//							┌───────────┐	└───────────┘
//;//							├───────────┤
//;//							src			src+count
//;//
//;//-----------------------------------------------------------------------------------------------------------------------------
//;//
//;//	(count == 0) が成立する場合も、memcpyを利用できる。その場合、
//;//		((dest <= src) || (dest >= src+count))　⇒　((dest <= src) || (dest >= src))　⇒　常に成立
//;//	となるので、明示的に判断する必要は無い。
//;//
//;//		○memcpyを利用できる
//;//
//;//			dest
//;//			│
//;//			│				
//;//							│
//;//							│
//;//							src(=src+count)
//;//
//;//		○memcpyを利用できる
//;//
//;//							dest
//;//							│
//;//							│
//;//							│
//;//							│
//;//							src(=src+count)
//;//
//;//		○memcpyを利用できる
//;//
//;//											dest
//;//											│
//;//											│
//;//							│
//;//							│
//;//							src(=src+count)
//;//
//;//-----------------------------------------------------------------------------------------------------------------------------
//;//
//;//	((dest <= src) || (dest >= src+count)) を効率良く判断する方法は、両辺からsrcを引いて、
//;//		((dest-src <= src-src) || (dest-src >= src-src+count))　⇒　((dest-src <= 0) || (dest-src >= count))
//;//	(dest-src) がマイナス値になる場合は、符号無し比較を用いれば (マイナス値 > count) が成立するから、
//;//	(dest-src >= count) に集約でき、(dest-src) が0になる場合だけを判断すれば良い。従って、
//;//		((dest-src == 0) || (dest-src >= count))
//;//	で判断できる。
//;//
//;//-----------------------------------------------------------------------------------------------------------------------------
//void* memmove(void* dest, const void* src, size_t count);
//asm("
//		.code
//		.align		1
//		.global		memmove
//memmove:
//		sub		%r12, %r13		;// %r12 := dest-src
//		jreq		2			;// if((dest-src == 0) ||	//jreq条件が成立しているので、xjrugeで必ず分岐する
//		cmp		%r12, %r14		;//    (dest-src >= count))
//		xjruge.d	memcpy			;//       return memcpy(dest, src, count)
//		add		%r12, %r13		;// %r12 := dest			*delay*
//		;//
//		add		%r12, %r14		;// %r12 := dest += count
//		add		%r13, %r14		;// %r13 := src  += count
//		;//
//		sub		%r13, 1			;// <---------------------------+
//memmove_LOOP:						;// do {			|
//;//		sub		%r13, 1			;//   %r13 := src--	--------+
//		ld.b		%r9, [%r13]		;//   %r9  := c = *src		|
//		sub		%r12, 1			;//   %r12 := dest--		|	*anti-interlock*
//		ld.b		[%r12], %r9		;//   *dest = c			|
//		sub		%r14, 1			;//   %r14 := count--		|
//		jrne.d		memmove_LOOP		;// } while(count != 0)		|
//		sub		%r13, 1			;// <---------------------------+	*delay*
//		ret.d					;// return  dest
//		ld.w		%r10, %r12		;// %r10 := dest			*delay*
//");
//}}2015/01/07削除:「EPSONライブラリの置き換え」の実装を、clippce.c⇒cliplibc.cへ移動しました。

//{{2015/01/07削除:「標準Ｃライブラリの補助」の実装を、clippce.c⇒cliplibc.cへ移動しました。
///*****************************************************************************
// *	標準Ｃライブラリの補助
// *****************************************************************************/
//
//#ifndef PIECE
//size_t strlen(const char* string) {
//	return strnlen(string, SIZE_MAX);
//}
///* strnlen()関数は、stringが指す文字列の長さを、バイト数で返す。
// * 長さには、終端の'\0'は含まない。
// * 長さは最大で、nまでである。
// * strnlen()関数は、stringの最初のnバイトのみを検査し、string+nより先を検査することはない。
// */
//size_t strnlen(const char* string, size_t n) {
//	size_t len = 0;
//	while((n--) && (*string++)) {
//		len++;
//	}
//	return len;
//}
//#else /*PIECE*/
//asm("
//		.code
//		.align		1
//		.global		strlen
//		.global		strnlen
//strlen:
//		ld.w		%r13, -1		;// %r13 := n = SIZE_MAX
//strnlen:
//		ld.w		%r10, -1		;// %r10 := len = -1	最後のループで余分に+1するので、予め-1しておく
//		sub		%r13, 1			;// %r13 := n--				<---------------+
//		jrult		4			;// if(n == -1) { return len + 1 }	--------+	|
//		ld.b		%r9, [%r12]+		;// %r9  := c = *string++			|	|
//		cmp		%r9, 0			;// if(c ==  0) { return len + 1 }		|	|
//		jrne.d		-4			;//					--------|-------+
//		add		%r10, 1			;// %r10 := len++			<-------+		*delay*
//		ret
//");
//#endif/*PIECE*/
//
//#ifndef PIECE
//char* strdup(const char* string) {
//	return strndup(string, SIZE_MAX);
//}
///* strndup()関数は、strdup()と同様であるが、最大でnバイトだけを複製する。
// * stringがnよりも長い場合は、nバイトだけを複製し、終端の'\0'を追加する。
// */
//char* strndup(const char* string, size_t n) {
//	size_t len = strnlen(string, n);
//	char* p =  malloc(len + 1/*終端の'\0'の分、忘れずに!*/);
//	if(p != NULL) {
//		p[len] = '\0';
//		memcpy(p, string, len);
//	}
//	return p;
//}
//#else /*PIECE*/
//asm("
//		.code
//		.align		1
//		.global		strdup
//		.global		strndup
//strdup:
//		ld.w		%r13, -1		;// %r13 := n = SIZE_MAX
//strndup:
//		pushn		%r1
//		;// %r12 := string
//		;// %r13 := n
//		xcall.d		strnlen			;// %r10 := len = strnlen(string, n)
//		ld.w		%r0, %r12		;// %r0  :=               string	*delay*
//		ld.w		%r1, %r10		;// %r1  := len
//		;// %r0  := string
//		;// %r1  := len
//		ld.w		%r12, %r1		;// %r12 :=             len
//		xcall.d		malloc			;// %r10 := p =  malloc(len + 1)
//		add		%r12, 1			;// %r12 :=             len + 1		*delay*
//		cmp		%r10, 0			;// if(p != NULL) {
//		jreq		strndup_RET
//		;// %r0  := string
//		;// %r1  := len
//		;// %r10 := p
//		ld.w		%r12, %r10		;//   %r12 :=            p
//		add		%r10, %r1		;//   %r10 := p + len
//		ld.b		[%r10], %r8		;//         *(p + len) = '￥0'		■注1■
//		ld.w		%r13, %r0		;//   %r13 :=               string
//		xcall.d		memcpy			;//   %r10 := p = memcpy(p, string, len)
//		ld.w		%r14, %r1		;//   %r14 :=                       len	*delay*
//strndup_RET:	;// %r10 := p				;// }
//		popn		%r1
//		ret					;// return p
//");
////■注1■
//// - asmブロック内のコメントに、半角「\」を使用しないよう、注意してください。
//// - たとえば、上記コメント内の「*(p+len)='￥0'」を「*(p+len)='\0'」とすると、そこでasmブロックが終了してしまいます。
////   なぜなら、コンパイラはasmブロックを文字列リテラルとして扱い、'\0' をエスケープシーケンスとして展開するからです。
//// - 上記コードの場合は、たまたま、strndup_RET の未定義エラーとなり、リンク時に発見できます。
////   しかし、常にビルド時のエラーとして発見できるとは限らず、見つけ辛いバグの原因となります。
//#endif/*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
//#ifndef PIECE
//int strcasecmp(const char* s1, const char* s2) {
//	int c1, c2;
//	do {
//		c1 = tolower(*s1++ & 0xff);
//		c2 = tolower(*s2++ & 0xff);
//	} while(c1 != '\0' && c1 == c2);
//	return c1 - c2;
//}
//int strncasecmp(const char* s1, const char* s2, size_t n) {
//	int c1, c2;
//	do {
//		if(n-- == 0) return 0;
//		c1 = tolower(*s1++ & 0xff);
//		c2 = tolower(*s2++ & 0xff);
//	} while(c1 != '\0' && c1 == c2);
//	return c1 - c2;
//}
//#else /*PIECE*/
///* * Fri Apr 20 11:09:47 JST 2012 Naoyuki Sawa
// * - strcasecmp()とstrncasecmp()をアセンブラ化しました。
// */
//asm("
//		.code
//		.align		1
//		.global		strcasecmp
//		.global		strncasecmp
//strcasecmp:
//		ld.w		%r14, -1		;// %r14 := n = UINT_MAX
//strncasecmp:
//		pushn		%r3
//		ld.w		%r1, %r12		;// %r1  := s1
//		ld.w		%r2, %r13		;// %r2  := s2
//		ld.w		%r3, %r14		;// %r3  := n
//		sub		%r3, 1			;// <-------------------------------------------+
//strncasecmp_LOOP:					;//						|
//;//		sub		%r3, 1			;// %r3  := n--	--------------------------------+
//		jrult		strncasecmp_RET0	;// if(n < 0) return 0				|
//		;//					;//						|
//		ld.ub		%r12, [%r2]+		;// %r12 := c2 = (unsigned char)*s2++		|
//		xcall		tolower			;// %r10 := c2 = tolower(c2)			|
//;//		ld.w		%r0, %r10		;// %r0  := c2	------------------------+	|
//		;//					;//					|	|
//		ld.ub		%r12, [%r1]+		;// %r12 := c1 = (unsigned char)*s1++	|	|
//		xcall.d		tolower			;// %r10 := c1 = tolower(c1)		|	|
//		ld.w		%r0, %r10		;// <-----------------------------------+	|	*delay*
//		;//					;//						|
//		cmp		%r10, 0			;// if(c1 ==  0) return (c1 - c2)		|
//		jreq.d		strncasecmp_EXIT	;//						|
//		sub		%r10, %r0		;// %r10 :=             (c1 - c2)		|	*delay*
//		jreq.d		strncasecmp_LOOP	;// if(c1 != c2) return (c1 - c2)		|
//		sub		%r3, 1			;// <-------------------------------------------+	*delay*
//strncasecmp_EXIT:
//		popn		%r3
//		ret
//strncasecmp_RET0:
//		jp.d		strncasecmp_EXIT
//		ld.w		%r10, 0			;// %r10 := 0						*delay*
//");
//#endif /*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
///* * Sat Dec 15 14:14:55 JST 2012 Naoyuki Sawa
// * - strcmp()とstrncmp()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
// *   EPSON版は、strcmp()とstrncmp()が個別に実装されていますが、まとめて実装し、少し省メモリになりました。
// * - 実際には、本モジュールに含めると、必要の無い場合も必ずリンクされてしまい、却ってメモリ消費が増えます。
// *   アプリケーションが、strcmp()とstrncmp()を両方使用している場合のみ、省メモリになったと考えてください。
// */
//#ifndef PIECE
//int strcmp(const char* s1, const char* s2) {
//	return strncmp(s1, s2, SIZE_MAX);
//}
//int strncmp(const char* s1, const char* s2, size_t n) {
//	int t = 0; /* 引数nに0が指定された場合に戻り値を0とするため、(t=0)に初期化しておく。 */
//	for(;;) {
//		int c1, c2;
//		if((int)--n == -1) {
//			break; /* この位置では必ず(t==0)である。従って"return 0"に相当する。 */
//		}
//		c1 = *(unsigned char*)s1++;
//		c2 = *(unsigned char*)s2++;
//		if((t = c1 - c2) != 0) {
//			break;
//		}
//		if(c2 == 0) {
//			break; /* この位置では必ず(t==0)である。従って"return 0"に相当する。 */
//		}
//	}
//	return t;
//}
//#else /*PIECE*/
//int  strcmp(const char* s1, const char* s2);
//int strncmp(const char* s1, const char* s2, size_t n);
//asm("
//		.code
//		.align		1
//		.global		strcmp
//		.global		strncmp
//;//---------------------------------------------;//
//strcmp:						;//
//		ld.w		%r14, -1	;// %r14 := n = SIZE_MAX
//;//---------------------------------------------;//
//strncmp:					;//
//		ld.w		%r10, 0		;// %r10 := t = 0
//strncmp_LOOP:					;// for(;;) {
//		sub		%r14, 1		;//   if((int)--n == -1)
//		jrult		strncmp_RET	;//     break
//		ld.ub		%r10, [%r12]+	;//   %r10 := c1 = *(unsigned char*)s1++
//		ld.ub		%r11, [%r13]+	;//   %r11 := c2 = *(unsigned char*)s2++
//		sub		%r10, %r11	;//   %r10 := t  = c1 - c2
//		jrne		strncmp_RET	;//   if(t  != 0) break
//		cmp		%r11, 0		;//   if(c2 == 0)
//		jrne		strncmp_LOOP	;//     break
//strncmp_RET:					;// }
//		ret
//;//---------------------------------------------;//
//");
//#endif /*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
///* * Sat Dec 15 14:14:55 JST 2012 Naoyuki Sawa
// * - strchr()とstrrchr()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
// *   EPSON版のstrchr()とstrrchr()で処理に無駄がある点を改善し、今回の実装の方が少し省メモリになりました。
// * - 実際には、本モジュールに含めると、必要の無い場合も必ずリンクされてしまい、却ってメモリ消費が増えます。
// *   アプリケーションが、strchr()とstrrchr()を両方使用している場合のみ、省メモリになったと考えてください。
// */
//#ifndef PIECE
//char* strchr(const char* s, int c) {
//	int tmp;
//	do {
//		if((tmp = *(unsigned char*)s) == c) {
//			return (char*)s; /* 文字列s中に最初に文字cが現れた位置へのポインタを返す。 */
//		}
//		s++;
//	} while(tmp != 0);
//	return NULL; /* 文字列s中に文字cが見つからない場合はNULLを返す。 */
//}
//char* strrchr(const char* s, int c) {
//	char* result = NULL; /* 文字列s中に最後に文字cが現れた位置へのポインタ。文字列s中に文字cが見つからない場合はNULLを返す。 */
//	int tmp;
//	do {
//		if((tmp = *(unsigned char*)s) == c) {
//			result = (char*)s; /* 文字列s中に最後に文字cが現れた位置へのポインタを更新する。 */
//		}
//		s++;
//	} while(tmp != 0);
//	return result; /* 文字列s中に最後に文字cが現れた位置へのポインタを返す。文字列s中に文字cが見つからない場合はNULLを返す。 */
//}
//#else /*PIECE*/
//char*  strchr(const char* s, int c);
//char* strrchr(const char* s, int c);
//asm("
//		.code
//		.align		1
//		.global		strchr
//		.global		strrchr
//;//---------------------------------------------;//
//;// strchr()関数は、文字列s中に最初に文字cが現れた位置へのポインタを返す。
//;// 文字列s中に文字cが見つからない場合はNULLを返す。
//;// 終端のnulは文字列の一部と見なされ、cにnulが指定された場合は終端のnulへのポインタを返す。←←←要注意!!
//;//---------------------------------------------;//
//strchr:						;// do {
//		ld.ub		%r10, [%r12]	;//   %r10 := tmp = *(unsigned char*)s
//		cmp		%r10, %r13	;//   if(tmp == c)			!INTERLOCK!
//		jreq		strchr_RET	;//     goto strchr_RET
//		cmp		%r10, 0		;//   
//		jrne.d		strchr		;// } while(tmp != 0)
//		add		%r12, 1		;//   %r12 := s++			*delay*
//		ret				;// return  0
//strchr_RET:					;//
//		ret.d				;// return  s
//		ld.w		%r10, %r12	;// %r10 := s				*delay*
//;//---------------------------------------------;//
//;// strrchr()関数は、文字列s中に最後に文字cが現れた位置へのポインタを返す。
//;// 文字列s中に文字cが見つからない場合はNULLを返す。
//;// 終端のnulは文字列の一部と見なされ、cにnulが指定された場合は終端のnulへのポインタを返す。←←←要注意!!
//;//---------------------------------------------;//
//strrchr:					;//
//		ld.w		%r10, 0		;// %r10 := result = 0
//strrchr_LOOP:					;// do {
//		ld.ub		%r9, [%r12]	;//   %r9  := tmp = *(unsigned char*)s
//		cmp		%r9, %r13	;//   					!INTERLOCK!
//		jrne.d		3		;//   if(tmp == c)
//		cmp		%r9, 0		;//   <-------------------------+	*delay*
//		 ld.w		%r10, %r12	;//     %r10 := result = s	|
//;//		cmp		%r9, 0		;//   --------------------------+
//		jrne.d		strrchr_LOOP	;// } while(tmp != 0)
//		add		%r12, 1		;//   %r12 := s++			*delay*
//		ret				;// return result
//;//---------------------------------------------;//
//");
//#endif /*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
///* * Tue Dec 18 21:23:57 JST 2012 Naoyuki Sawa
// * - memchr()とmemcmp()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
// *   EPSON版のmemchr()とmemcmp()で処理に無駄がある点を改善し、今回の実装の方が4バイト省メモリになりました。
// * - 本モジュールに含めると、必要の無い場合も必ずリンクされ、却ってメモリ消費が増える恐れがあります
// *   しかし、memchr()はdoprnt.cモジュールから,memcmp()はclippce.cから参照されているので、元々必ず必要です。
// *   従って、無駄は生じません。
// */
//#ifndef PIECE
//void* memchr(const void* _s, int c, size_t n) {
//	unsigned char* s      = (unsigned char*)_s;
//	unsigned char* result = (unsigned char*) 1;
//	int tmp;
//	for(;;) {
//		if((int)--n == -1) {
//			break; /* この位置では必ず(result==1)である。 */
//		}
//		if((tmp = *s++) == c) {
//			result = s; /* ポインタsは文字cを見つけた位置よりも+1進んでいる。 */
//			break;
//		}
//	}
//	return result - 1; /* どちらのbreakで抜けた場合も(result-1)が正しい戻り値となる。 */
//}
//int memcmp(const void* _s1, const void* _s2, size_t n) {
//	const unsigned char* s1 = _s1;
//	const unsigned char* s2 = _s2;
//	int c1, c2, t;
//	for(;;) {
//		if((int)--n == -1) {
//			break;
//		}
//		c1 = *s1++;
//		c2 = *s2++;
//		if((t = c1 - c2) != 0) {
//			return t;
//		}
//	}
//	return 0;
//}
//#else /*PIECE*/
//void* memchr(const void* _s, int c, size_t n);
//int memcmp(const void* _s1, const void* _s2, size_t n);
//asm("
//		.code
//		.align		1
//		.global		memchr
//		.global		memcmp
//;//---------------------------------------------;//
//memchr:						;//
//		ld.w		%r10, 1		;// %r10 := result = 1
//memchr_LOOP:					;// for(;;) {
//		sub		%r14, 1		;//   if((int)--n == -1)
//		jrult		memchr_RET	;//     break
//		ld.ub		%r9, [%r12]+	;//   %r9  := tmp = *s++
//		cmp		%r9, %r13	;//   if(tmp == c) {
//		jrne		memchr_LOOP	;//     %r10 := result = s
//		ld.w		%r10, %r12	;//     break }
//memchr_RET:					;// }
//		ret.d				;// return  result
//		sub		%r10, 1		;// %r10 := result--	*delay*
//;//---------------------------------------------;//
//memcmp:						;// for(;;) {
//		sub		%r14, 1		;//   if((int)--n == -1)
//		jrult		memcmp_RET0	;//     break
//		ld.ub		%r10, [%r12]+	;//   %r10 := c1 = *(unsigned char*)s1++
//		ld.ub		%r11, [%r13]+	;//   %r11 := c2 = *(unsigned char*)s2++
//		sub		%r10, %r11	;//   %r10 := t  = c1 - c2
//		jreq		memcmp		;//   if(t != 0)
//		ret				;//     return t
//memcmp_RET0:					;// }
//		ret.d				;// return  0
//		ld.w		%r10, 0		;// %r10 := 0		*delay*
//;//---------------------------------------------;//
//");
//#endif /*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
///* * Fri Dec 21 00:22:01 JST 2012 Naoyuki Sawa
// * - strspn()とstrcspn()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
// *   EPSON版のmemchr()とmemcmp()で処理に無駄がある点を改善し、今回の実装の方が4命令分省メモリになりました。
// * - 本モジュールに含めると、必要の無い場合も必ずリンクされ、却ってメモリ消費が増える恐れがあります
// *   しかし、strspn()とstrcspn()は、clippce.c内で再実装したstrtok()から参照しているので、元々必ず必要です。
// *   従って、無駄は生じません。
// */
//#ifndef PIECE
//size_t  strspn(const char* s1, const char* s2) {
//	int result = -1;
//	int c1, c2, c;
//	const char* p;
//	c2 = 0;					//異なるのはこの二行だけ
//	for(;;) {
//		c1 = *(unsigned char*)s1++;	//異なるのはこの二行だけ
//		//{{--- strspn_strcspn ---
//		result++;
//		if(c1 == c2) { return result; }
//		p = s2;
//		for(;;) {
//			c = *(unsigned char*)p++;
//			if(c == c1) { break; }
//			if(c == c2) { return result; }
//		}
//		//}}--- strspn_strcspn ---
//	}
//}
//size_t strcspn(const char* s1, const char* s2) {
//	int result = -1;
//	int c1, c2, c;
//	const char* p;
//	c1 = 0;					//異なるのはこの二行だけ
//	for(;;) {
//		c2 = *(unsigned char*)s1++;	//異なるのはこの二行だけ
//		//{{--- strspn_strcspn ---
//		result++;
//		if(c1 == c2) { return result; }
//		p = s2;
//		for(;;) {
//			c = *(unsigned char*)p++;
//			if(c == c1) { break; }
//			if(c == c2) { return result; }
//		}
//		//}}--- strspn_strcspn ---
//	}
//}
//#else /*PIECE*/
//size_t  strspn(const char* s1, const char* s2);
//size_t strcspn(const char* s1, const char* s2);
//asm("
//		.code
//		.align		1
//		.global		strspn
//		.global		strcspn
//strspn:
//		ld.w		%r10, -1		;// %r10 := result = -1
//		ld.w		%r5, 0			;// %r5  := c2 = 0
//		;//					;// for(;;) {
//		ld.ub		%r4, [%r12]+		;//   %r4  := c1 = *(unsigned char*)s1++
//		call		strspn_strcspn		;//   %r10 := result++
//		jp		-2			;// }
//		;//-------------------------------------;//
//strcspn:
//		ld.w		%r10, -1		;// %r10 := result = -1
//		ld.w		%r4, 0			;// %r4  := c1 = 0
//		;//					;// for(;;) {
//		ld.ub		%r5, [%r12]+		;//   %r5  := c2 = *(unsigned char*)s1++
//		call		strspn_strcspn		;//   %r10 := result++
//		jp		-2			;// }
//		;//-------------------------------------;//
//strspn_strcspn:
//		cmp		%r4, %r5		;// if(c1 == c2)
//		jreq.d		8			;//   return result	--------------------------------+	strspn又はstrcspnへ戻らず、その呼び出し元へ戻る
//		add		%r10, 1			;// %r10 := result++	*delay*				|
//		ld.w		%r6, %r13		;// %r6  := p = s2					|
//		;//					;// for(;;) {						|
//		ld.ub		%r7, [%r6]+		;//   %r7  := c = *(unsigned char*)p++	<-------+	|
//		cmp		%r7, %r4		;//   if(c == c1)				|	|
//		jreq		4			;//     return result	----------------+	|	|	strspn又はstrcspnへ戻る
//		cmp		%r7, %r5		;//   if(c != c2)			|	|	|
//		jrne		-4			;//     continue	----------------|-------+	|
//		add		%sp, 1			;//   else		<---------------|---------------+
//		ret					;//     return result	<---------------+			strspn又はstrcspnへ戻らず、その呼び出し元へ戻る
//							;// }
//");
//#endif /*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
///* * Wed Mar 27 20:21:54 JST 2013 Naoyuki Sawa
// * - strsep()を追加しました。宣言は、include/string.hに追加しました。
// *   strsep()は、EPSONライブラリには含まれておらず、また、C言語標準関数ではありませんが、UNIX環境では一般的な関数です。
// *   strtok()と較べて、空のフィールドを扱えること，リエントラントであることの利点があり、便利なのでP/ECEにも追加することにしました。
// * - strpbrk()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
// *   strpbrk()は、EPSONライブラリの実装のままでも、充分にコンパクトで高速です。
// *   しかし、strsep()と一緒に実装することで、一部のレジスタの退避を省略してよりコンパクトで高速にできるので、strpbrk()も実装することにしました。
// *   実際には、strpbrk()を使用しないアプリケーションにもリンクされてしまう欠点を勘案すると、必ずしも効率的とは言えないのですけれど、
// *   EPSONライブラリ版よりも良いアセンブラ化ができた(と思う)ので、せっかくなので含めることにしました(^^;
// */
//#ifndef PIECE
///* strsep()関数は、*stringpにより参照される文字列の中で、文字列delim中のいずれかの文字(または終端の'\0')のうちで最初に出現する文字の位置を探し、その箇所を'\0'で置き換えます。
// * 区切り文字の次の文字の位置(または文字列の末尾に達した場合はNULL)が、*stringpに格納されます。
// * *stringpの元の値が、戻り値となります。
// * 「空」のフィールド、すなわち、2つの隣接する区切り文字によって生じたフィールドは、*stringpの中に返されたポインタが参照する位置を'\0'と比較することで検出可能です。
// * *stringpが最初からNULLである場合は、strsep()はNULLを返します。
// */
//char* strsep(char** stringp, const char* delim) {
//	char* begin = *stringp;
//	if(begin) {
//		char* end = strpbrk(begin, delim);
//		if(end) { *end++ = '\0'; }
//		*stringp = end;
//	}
//	return begin;
//}
//char* strpbrk(const char* s, const char* accept) {
//	const char* p;
//	for(;;) {
//		int c = *s++;
//		if(!c) { return NULL; }
//		p = accept;
//		for(;;) {
//			int t = *p++;
//			if(!t) { break; }
//			if(t == c) { return (char*)(s - 1); }
//		}
//	}
//}
//#else /*PIECE*/
//char* strsep(char** stringp, const char* delim);
//char* strpbrk(const char* s, const char* accept);
//asm("
//		.code
//		.align		1
//		;//=====================================;//
//		.global		strsep
//strsep:
//		ld.w		%r14, [%r12]		;// %r14 := begin = *stringp
//		cmp		%r14, 0			;// if(begin) {
//		jreq		strsep_RET
//		ld.w		%r15, %r12		;//   %r15 := stringp
//		call.d		strpbrk			;//   %r10 := end = strpbrk(begin, delim)
//		ld.w		%r12, %r14		;//   %r12 :=               begin		*delay*
//		cmp		%r10, 0			;//   if(end) {
//		jreq		2			;//     *end++ = '￥0'		--------+
//		ld.b		[%r10]+, %r8		;//   }					|
//		ld.w		[%r15], %r10		;//   *stringp = end		<-------+
//strsep_RET:	;//					;// }
//		ret.d
//		ld.w		%r10, %r14		;// %r10 := begin				*delay*
//		;//=====================================;//
//		.global		strpbrk
//strpbrk:						;// for(;;) {
//		ld.b		%r10, [%r12]+		;//   %r10 := c = *s++		<-------+
//		cmp		%r10, 0			;//   if(!c)				|
//		jreq		strpbrk_RET		;//     return NULL			|
//		ld.w		%r11, %r13		;//   %r11 := p = accept		|
//		;//					;//   for(;;) {				|
//		ld.b		%r9, [%r11]+		;//     %r9  := t = *p++	<-------|-------+
//		cmp		%r9, 0			;//     if(!t)				|	|
//		jreq		strpbrk			;//       break			--------+	|
//		cmp		%r9, %r10		;//     if(t == c) return s - 1			|
//		jrne		-4			;//   }				----------------+
//		sub		%r12, 1			;// %r12 := s - 1
//		ld.w		%r10, %r12		;// %r10 := s - 1
//strpbrk_RET:	;//					;// }
//		ret
//");
//#endif/*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
///* * Thu Oct 09 22:33:48 JST 2014 Naoyuki Sawa
// * - strcat()とstrncat()は、P/ECE開発環境のEPSONライブラリにも含まれていますが、自分で再実装しました。
// *   EPSON版は、strcat()とstrncat()が個別に実装されていますが、まとめて実装し、少し省メモリになりました。
// * - 実際には、本モジュールに含めると、必要の無い場合も必ずリンクされてしまい、却ってメモリ消費が増えます。
// *   アプリケーションが、strcat()とstrncat()を両方使用している場合のみ、省メモリになったと考えてください。
// * - 省メモリの効果は、以下の通りです。
// *   □EPSON版
// *   │strcat  10命令┐
// *   │strncat 16命令┴26命令
// *   □自作版            ↓…12命令節約
// *   │strcat＋strncat 14命令
// */
//#ifndef PIECE
///* □「Linux Programmer's Manual - STRCAT」(http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/strcat.3.html)より引用:
// * │strcat()関数は、dest文字列の後にsrc文字列を付け加える。
// * │その際に、destの最後にある終端のヌルバイト('\0')は上書きされ、新たに生成された文字列の末尾に終端のヌルバイトが付与される。
// * │二つの文字列srcとdestは重なってはならない。
// * │また、文字列destは、連結後の結果を格納するのに十分な大きさでなければならない。
// * │destが十分な大きさでない場合、プログラムがどのような動作をするか分からない。
// * │バッファオーバーランはセキュアなプログラムを攻撃する際に好んで使われる方法である。
// * │strncat()も同様だが、以下の点が異なる。
// * │- srcのうち最大nバイトが使用される。
// * │- srcがnバイト以上の場合、srcはヌル終端されている必要はない。
// * │strcat()と同じく、destに格納される結果の文字列は常にヌル終端される。
// * │srcがnバイト以上の場合、strncat()はdestにn+1バイトを書き込む(srcからのnバイトと終端のヌルバイトである)。
// * │したがって、destの大きさは最低でもstrlen(dest)+n+1でなければならない。 */
//char* strcat(char* dest, const char* src) {
//	return strncat(dest, src, SIZE_MAX);
//}
//char* strncat(char* dest, const char* src, size_t n) {
//	char* p = dest;
//	while(*p++) { /** no job **/ }
//	p--;
//	while(n--) { if(!(*p++ = *src++)) { return (char*)dest; } }
//	*p++ = '\0';
//	return (char*)dest;
//}
//#else /*PIECE*/
//char* strcat(char* dest, const char* src);
//char* strncat(char* dest, const char* src, size_t n);
////素直なバージョン(15命令)と、1命令節約バージョン(14命令)を作成しました。サイズを優先して後者を使用することにしました。
////strcat()やstrncat()を使用するような処理は、速度があまり重要でないことが多く、少しでも小さい方が良いと思ったからです。
//#if 0
////ほぼC言語どおり素直に実装したバージョンです。
//asm("
//		.code
//		.align		1
//		.global		strcat
//		.global		strncat
//strcat:
//		ld.w		%r14, -1		;//%r14 := n = SIZE_MAX
//strncat:
//		ld.w		%r10, %r12		;//%r12 := p = dest
//		ld.b		%r9, [%r12]+		;//while(*p++) { /** no job **/ }
//		 cmp		%r9, 0
//		jrne		-2
//		sub		%r12, 1			;//%r12 := p--
//strncat_LOOP:
//		sub		%r14, 1			;//while(n--) {
//		jrult		strncat_TERM
//		ld.b		%r9, [%r13]+
//		ld.b		[%r12]+, %r9		;//  if(!(*p++ = *src++)) { return dest }
//		cmp		%r9, 0
//		jrne		strncat_LOOP
//		ret					;//}
//strncat_TERM:
//		ld.b		[%r12], %r8		;//*p = '￥0'
//		ret					;//return dest
//");
//#else
////1命令節約したバージョン。ただし少し低速です。
//asm("
//		.code
//		.align		1
//		.global		strcat
//		.global		strncat
//strcat:
//		ld.w		%r14, -1		;//%r14 := n = SIZE_MAX
//strncat:
//		ld.w		%r10, %r12		;//%r12 := p = dest
//		ld.b		%r9, [%r12]		;//while(*p) { p++ }
//		 cmp		%r8, %r9		;//					%psr(C) := %r9 ? 1 : 0
//		jrne.d		-2
//strncat_LOOP:						;//for(;;) {
//		adc		%r12, %r8		;//  if(!初回) { p++ }			上のループ中は+1。上のループ抜け時のみ+0。下のループ中は+1。	*delay*
//		sub		%r14, 1			;//  if(!n--) { break }
//		jrult		strncat_TERM
//		ld.b		%r9, [%r13]+		;//  if(!(*p = *src++)) { break }
//		ld.b		[%r12], %r9
//		cmp		%r8, %r9		;//					%psr(C) := %r9 ? 1 : 0
//		jrne		strncat_LOOP		;//}
//strncat_TERM:
//		ld.b		[%r12], %r8		;//*p = '￥0'				下のループから降りて来た場合は、2回nulを格納することになるが問題無い。
//		ret					;//return dest
//");
//#endif
//#endif/*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
///* * Fri Oct 31 00:45:36 JST 2014 Naoyuki Sawa
// * - strlcpy()を追加しました。宣言は、include/string.hに追加しました。
// * □「Linux Programmer's Manual - STRCPY」(http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/strcpy.3.html)より引用:
// * │いくつかのシステム(BSD、Solarisなど)では以下の関数が提供されている。
// * │    size_t strlcpy(char *dest, const char *src, size_t size); 
// * │この関数はstrncpy()と同様だが、最大でもsize-1バイトしかdestにコピーをせず、末尾への終端のヌルバイトの追加が必ず行われ、宛先バッファ(の未使用部分)へのヌルバイトの書き込みが行われない。
// * │この関数ではstrcpy()やstrncpy()の持つ問題のいくつかが修正されているが、sizeが小さすぎた場合にはデータが失われる問題には、依然として呼び出し側で対処する必要がある。
// * │この関数の返り値はsrcの長さである。これにより、末尾の切り詰めが行われたかを簡単に検出することができる。返り値がsize以上の場合には、末尾の切り詰めが発生している。
// * │データロスが問題となる場合は、呼び出し側で、呼び出し前に引き数をチェックするか、この関数の返り値を検査するかのいずれかをしなければならない。
// * │strlcpy()はglibcには存在せず、POSIXによる標準化もされていないが、Linuxではlibbsdライブラリ経由で利用できる。 */
//#ifndef PIECE
//size_t strlcpy(char* dst, const char* src, size_t size) {
//	size_t len = strlen(src);			//転送元の文字列の長さを求める。
//	if(size--) {					//バッファサイズを1減らす。1減らす前のバッファサイズが0でなければ…
//		if(size > len) { size = len; }		//転送する文字列の長さを求める。
//		memcpy(dst, src, size);			//文字列を転送する。
//		dst[size] = '\0';			//文字列をヌル終端する。
//	}
//	return len;					//転送元の文字列の長さを返す。
//}
//#else /*PIECE*/
//size_t strlcpy(char* dst, const char* src, size_t size);
//asm("
//		.code
//		.align		1
//		.global		strlcpy
//strlcpy:
//		pushn		%r2
//		ld.w		%r0, %r12		;//%r0  := dst
//		ld.w		%r1, %r13		;//%r1  := src
//		ld.w		%r2, %r14		;//%r2  := size
//		xcall.d		strlen			;//%r10 := len = strlen(src)
//		ld.w		%r12, %r1		;//%r12 :=              src			*delay*
//		sub		%r2, 1			;//%r2  := size--
//		jrult		strlcpy_RET		;//if(size >= 0) {
//		ld.w		%r12, %r0		;//  %r12 :=              dst
//		ld.w		%r0, %r10		;//  %r0  := len
//		cmp		%r2, %r0		;//  if(size > len) {
//		jrule.d		3			;//
//		 ld.w		%r13, %r1		;//  %r13 :=                   src		*delay*
//		 ld.w		%r2, %r0		;//     size = len  }
//		xcall.d		memcpy			;//  %r10 := dst = memcpy(dst, src, size)
//		ld.w		%r14, %r2		;//  %r14 :=                        size	*delay*
//		add		%r10, %r2		;//  %r10 := &dst[size]
//		ld.b		[%r10], %r8		;//           dst[size] = '￥０'
//		ld.w		%r10, %r0		;//  %r10 := len
//strlcpy_RET:						;//}
//		popn		%r2
//		ret
//");
//#endif/*PIECE*/
//
///* * Fri Oct 31 00:45:36 JST 2014 Naoyuki Sawa
// * - strlcat()を追加しました。宣言は、include/string.hに追加しました。
// * □「Linux Programmer's Manual - STRCAT」(http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/strcat.3.html)より引用:
// * │いくつかのシステム(BSD、Solarisなど)では以下の関数が提供されている。
// * │    size_t strlcat(char *dest, const char *src, size_t size); 
// * │この関数は、ヌル終端された文字列srcを文字列destに追加する。
// * │具体例には、sizeがstrlen(dest)より大きい場合には最大でsize-strlen(dest)-1バイトをsrcからコピーし、結果の末尾に終端のヌルバイトを追加する。
// * │この関数ではstrcat()のバッファオーバーランが発生するという問題が修正されているが、sizeが小さすぎた場合にはデータが失われる問題には、依然として呼び出し側で対処する必要がある。
// * │この関数はstrlcat()が作成しようとした文字列の長さを返す。返り値がsize以上の場合、データロスが発生している。
// * │データロスが問題となる場合は、呼び出し側で、呼び出し前に引き数をチェックするか、この関数の返り値を検査するかのいずれかをしなければならない。
// * │strlcat()はglibcには存在せず、POSIXによる標準化もされていないが、Linuxではlibbsdライブラリ経由で利用できる。 */
//#ifndef PIECE
//size_t strlcat(char* dst, const char* src, size_t size) {
//	size_t dlen = strlen(dst);			//転送先の文字列の長さを求める。
//	size_t slen = strlen(src);			//転送元の文字列の長さを求める。
//	size_t tlen = dlen + slen;			//文字列の長さの合計を求める。
//	if(size > dlen) {				//転送先の文字列がバッファ内でヌル終端されていたら…
//		size -= dlen;				//(バッファ内に追加可能な文字数+1)を求める。
//		if(slen >= size) { slen = size - 1; }	//追加する文字列の長さを求める。
//		dst += dlen;				//文字列を追加する位置を求める。
//		memcpy(dst, src, slen);			//文字列を追加する。
//		dst[slen] = '\0';			//文字列をヌル終端する。
//	}
//	return tlen;					//文字列の長さの合計を返す。
//}
//#else /*PIECE*/
//size_t strlcat(char* dst, const char* src, size_t size);
//asm("
//		.code
//		.align		1
//		.global		strlcat
//strlcat:
//		pushn		%r3
//		ld.w		%r0, %r12		;//%r0  := dst
//		ld.w		%r1, %r13		;//%r1  := src
//		xcall.d		strlen			;//%r10 := dlen = strlen(dst)
//		ld.w		%r2, %r14		;//%r2  := size					*delay*
//		add		%r0, %r10		;//%r0  := dst += dlen
//		ld.w		%r3, %r10		;//%r3  := dlen
//		xcall.d		strlen			;//%r10 := slen = strlen(src)
//		ld.w		%r12, %r1		;//%r12 :=               src			*delay*
//		sub		%r2, %r3		;//%r2  := size -= dlen
//		jrule.d		strlcat_RET		;//if(size > 0) {
//		add		%r3, %r10		;//%r3  := tlen = dlen + slen			*delay*
//		cmp		%r10, %r2		;//  if(slen >= size) {
//		jrult.d		4			;//
//		 ld.w		%r12, %r0		;//  %r12 :=        dst				*delay*
//		 ld.w		%r10, %r2		;//    %r10 := slen = size - 1
//		 sub		%r10, 1			;//  }
//		ld.w		%r13, %r1		;//  %r13 :=             src
//		ld.w		%r14, %r10		;//  %r14 :=                  slen
//		xcall.d		memcpy			;//  %r10 := memcpy(dst, src, slen)
//		add		%r0, %r10		;//  %r0  := &dst[slen]				*delay*
//		ld.b		[%r0], %r8		;//           dst[slen] = '￥０'
//strlcat_RET:						;//}
//		ld.w		%r10, %r3		;//%r10 := tlen
//		popn		%r3
//		ret
//");
//#endif/*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
//int
//ismbblead(int c)
//{
//	c &= 0xff; /* bit31-8は無視（VC++の_ismbblead()もそうなってます） */
//	return (0x81 <= c && c <= 0x9f) ||
//	       (0xe0 <= c && c <= 0xfc);
//}
//
//int
//ismbbtrail(int c)
//{
//	c &= 0xff; /* bit31-8は無視（VC++の_ismbbtrail()もそうなってます） */
//	return (0x40 <= c && c <= 0x7e) ||
//	       (0x80 <= c && c <= /*0xec*/0xfc);
//	/* 2002/12/12: VC++6.0付属のMSDNの、_ismbbtrail()の説明はうそです。 */
//	/* 後半0x80～0xecでは、「ん」(0x82f1)の二バイト目が判定できません。 */
//}
//
///*--------------------------------------------------------------------------*/
//
//#ifndef PIECE
//int iscsym(int c) {
//	return isalnum(c) || (c == '_');
//}
//int iscsymf(int c) {
//	return isalpha(c) || (c == '_');
//}
//#else /*PIECE*/
///* * Fri Apr 20 11:09:47 JST 2012 Naoyuki Sawa
// * - iscsym()とiscsymf()をアセンブラ化しました。
// */
//asm("
//		.code
//		.align		1
//		.global		iscsym
//		.global		iscsymf
//iscsym:
//		xcmp		%r12, 95	;// if(c != '_')
//		xjrne		isalnum		;//   return isalnum(c)
//		ret.d				;// else
//		ld.w		%r10, 1		;//   return 1	*delay*
//iscsymf:
//		xcmp		%r12, 95	;// if(c != '_')
//		xjrne		isalpha		;//   return isalpha(c)
//		ret.d				;// else
//		ld.w		%r10, 1		;//   return 1	*delay*
//");
//#endif /*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
//#ifndef PIECE
//int isblank(int c) {
//	return (c == ' ') || (c == '\t');
//}
//#else /*PIECE*/
//asm("
//		.code
//		.align		1
//		.global		isblank
//isblank:
//		;// xcmpで比較すると、' 'の比較時に、ext拡張が必要になります。
//		;// subとaddを使うと、ext拡張が不要で、1命令分高速化できます。
//		sub		%r12, 32	;// if((c -=  ' '        ) == 0) { %psr[Z] = 1 } else
//		jreq		2
//		 add		%r12, 23	;// if((c += (' ' - '\t')) == 0) { %psr[Z] = 1 } else { %psr[Z] = 0 }
//		ld.w		%r10, %psr	;// %r10[1] := %psr[Z]
//		ret.d
//		and		%r10, 2		;// return     %r10[1] … 0 or 2	*delay*		戻り値が(0|1)でなく(0|2)となることに注意。isblank()の仕様には反しない。
//");
//#endif /*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
////{{2014/01/13削除:lfind()とlsearch()の実装を、clippce.c⇒clipsrch.cへ分離しました。
/////* * Sat Jul 04 21:25:58 JST 2009 Naoyuki Sawa
//// * - POSIX標準のリニアサーチ関数です。
//// *   やや新しい標準のようで、P/ECE開発環境のEPSONライブラリには含まれていなかったので、ここで実装しました。
//// *   単純な処理ですが、未ソートの配列をリニアサーチしたい(=bsearch()は使えない)場合は結構有るので、便利だと思います。
//// * - 関数仕様は、Web上の資料や、MSDNなどを参照してください。(Visual C++ 6.0のMSDNにも載っています。)
//// *   lfind()の引数で、numがポインタ渡しなのが無駄に見えますが、おそらく、lsearch()と引数型を一致させたためだと思います。
//// *   また、base引数が、lfind()はconst、lsearch()は非constである違いに注意してください。
//// *   lfind()は配列を変更せず、lsearch()は見つからなかったときに配列に要素を追加することを、明示するためだと思います。
//// *   (その割には、lfind()の戻り値の型は非constになっている点が、一貫性がありませんが…)
//// * - lsearch()を使って、簡単に、重複の無いセットを作成することができます。
//// *   以下に、使用例を示します。
//// *	<例>
//// *		static int compare(const void* elem1, const void* elem2) {
//// *			char* str1 = *(char**)elem1;
//// *			char* str2 = *(char**)elem2;
//// *			return strcmp(str1, str2);
//// *		}
//// *		void sample() {
//// *			char* set[10];
//// *			int num = 0;
//// *			char* key;
//// *			int i;
//// *			if(num < 10) key = "One";   lsearch(&key, set, &num, sizeof set[0], compare);
//// *			if(num < 10) key = "Two";   lsearch(&key, set, &num, sizeof set[0], compare);
//// *			if(num < 10) key = "One";   lsearch(&key, set, &num, sizeof set[0], compare);
//// *			if(num < 10) key = "Three"; lsearch(&key, set, &num, sizeof set[0], compare);
//// *			if(num < 10) key = "Two";   lsearch(&key, set, &num, sizeof set[0], compare);
//// *			if(num < 10) key = "Four";  lsearch(&key, set, &num, sizeof set[0], compare);
//// *			for(i = 0; i < num; i++) { printf("%d: %s\n", i, set[i]); }
//// *		}
//// *	<結果>
//// *		0: One
//// *		1: Two
//// *		2: Three
//// *		3: Four
//// */
////void*
////lfind(const void* key, const void* base, size_t* num, size_t width, int (*compare)(const void* elem1, const void* elem2))
////{
////	int i = *num;
////	while(i) {
////		if(!(*compare)(key, base)) {
////			return (void*)base;
////		}
////		base = (char*)base + width;
////		i--;
////	}
////	return NULL;
////}
////void*
////lsearch(const void* key, void* base, size_t* num, size_t width, int (*compare)(const void* elem1, const void* elem2))
////{
////	void* ptr = lfind(key, base, num, width, compare);
////	if(!ptr) {
////		ptr = memcpy((char*)base + ((*num)++ * width), key, width);
////	}
////	return ptr;
////}
////}}2014/01/13削除:lfind()とlsearch()の実装を、clippce.c⇒clipsrch.cへ分離しました。
//
///* * Sat Jul 04 21:25:58 JST 2009 Naoyuki Sawa
// * - 勉強のため自分で実装してみましたが、bsearch()はP/ECE開発環境のEPSONライブラリにも含まれており、以下のコードと全く同じ実装です。
// *   以下のコードをclippce.cに含めておくと、アプリケーションに常にリンクされてコードサイズが無駄になるので、コメントアウトしました。
// *   なお、Visual C++ 6.0にもbsearch()のソースが含まれており、参考にできますが、EPSONライブラリ(=以下のコードと同じ)の方が簡潔です。
// */
////	void*
////	bsearch(const void* key, const void* base, size_t num, size_t width, int (*compare)(const void* elem1, const void* elem2))
////	{
////		while(num) {
////			char* mid = (char*)base + ((num >> 1) * width);
////			int retval = (*compare)(key, mid);
////			if(retval >= 0) {
////				if(!retval) {
////					return mid;
////				}
////				base = mid + width;
////				num--;
////			}
////			num >>= 1;
////		}
////		return NULL;
////	}
//
///*--------------------------------------------------------------------------*/
//
///* * Mon Jul 06 23:33:17 JST 2009 Naoyuki Sawa
// * - strupr()とstrlwr()は、標準関数かどうかよくわからず、VC++特有なのかも知れません。
// *   Web上の資料から推測すると、Unix環境のライブラリには含まれていない(?)ようです。
// *   P/ECE開発環境のEPSONライブラリにも、strupr()とstrlwr()は、含まれていません。
// *   便利なので、定義しておくことにしました。
// * - strupr()とstrlwr()のコードは小さいので、無駄なコードサイズ増の影響は僅かです。
// *   tolower()を呼び出していますが、tolower()は既に本モジュールで使用しているので、新たなコード増は生じません。
// *   toupper()を呼び出していますが、toupper()は必須のclipprnt.cで使用しているので、新たなコード増は生じません。
// */
////#ifndef PIECE
////char* strupr(char* string) {
////	int c;
////	char* p = string;
////	while((c = *p)) {
////		*p++ = toupper(c);
////	}
////	return string;
////}
////char* strlwr(char* string) {
////	int c;
////	char* p = string;
////	while((c = *p)) {
////		*p++ = tolower(c);
////	}
////	return string;
////}
////#else /*PIECE*/
////char* strupr(char* string);
////char* strlwr(char* string);
////asm("
////		.code
////		.align		1
////		.global		strupr
////		.global		strlwr
////strupr:
////		xld.w		%r13, toupper		;// %r13 := fn = toupper
////		jp		strupr_strlwr
////strlwr:
////		xld.w		%r13, tolower		;// %r13 := fn = tolower
////strupr_strlwr:
////		pushn		%r2
////		ld.w		%r0, %r12		;// %r0  := string
////		ld.w		%r1, %r12		;// %r1  := p
////		ld.w		%r2, %r13		;// %r2  := fn
////strupr_strlwr_LOOP:					;// for(;;) {
////		ld.b		%r12, [%r1]		;//   %r12 := c = *p
////		cmp		%r12, 0			;//   if(!c)
////		jreq		strupr_strlwr_RET	;//     break
////		call		%r2			;//   %r10 := c = fn(c)
////		ld.b		[%r1]+, %r10		;//   *p++ := c
////		jp		strupr_strlwr_LOOP	;// }
////strupr_strlwr_RET:
////		ld.w		%r10, %r0		;// %r10 := string
////		popn		%r2
////		ret
////");
////#endif /*PIECE*/
////↓---------------------------------------------------------------------------------------------------------------------
////↓ * Sat Dec 25 12:48:38 JST 2010 Naoyuki Sawa
////↓ - strupr()やstrlwr()で、全角文字を含む文字列を変換すると、文字化けすることがある問題を修正しました。
////↓ - これまでの版では、たとえば「テキスト」という文字列を、toupper()で変換すると「ウキスエ」になってしまっていました。
////↓   「テ」の2バイト目を「e」と見なして「E」に、「ト」の2バイト目を「g」と見なして「E」に変換してしまっていたためです。
////↓ - 今回の修正によって、全角文字を含む文字列も、strupr()やstrlwr()で正しく変換できるようになりました。
////↓ - なお、strupr()やstrlwr()で全角文字が化ける問題は、Visual C++ 6.0や、Borland C++ 5.5.1のライブラリでも発生します。
////↓   Windowsプログラミングの際には、気をつけてください。
////↓   回避策の一つは、MFCのCString::MakeUpper()を使うことです。Visual C++ 6.0のMFCで、文字化けしないことを確認しました。
////↓---------------------------------------------------------------------------------------------------------------------
//#ifndef PIECE
//char* strupr(char* string) {
//	char* p = string;
//	for(;;) {
//		/* 全角文字ならば、変換せずに飛ばす。
//		 * - 1バイト目がismbblead()でtrueならば、2バイト目を調べなくても全角文字と判断できるのですが、
//		 *   壊れた文字列で2バイト目がnulだった場合に、バッファオーバーランするおそれがあるため、
//		 *   安全を期して、2バイト目がismbbtrail()でtrueであることも確認することにしました。
//		 */
//		if(ismbblead(p[0]) && ismbbtrail(p[1])) {
//			p += 2;
//		/* 半角文字ならば、変換する。終端だったら抜ける。
//		 * - toupper()はnulを変換しないので、終端チェックはtoupper()より先に行っても後に行っても同じです。
//		 *   toupper()よりも後で行う方が、Asm化した時に条件ループを組み易いため、後で行うことにしました。
//		 */
//		} else {
//			if(!(*p = toupper(*p))) break;
//			p += 1;
//		}
//	}
//	return string;
//}
//char* strlwr(char* string) {
//	char* p = string;
//	for(;;) {
//		/* 全角文字ならば、変換せずに飛ばす。
//		 * - 1バイト目がismbblead()でtrueならば、2バイト目を調べなくても全角文字と判断できるのですが、
//		 *   壊れた文字列で2バイト目がnulだった場合に、バッファオーバーランするおそれがあるため、
//		 *   安全を期して、2バイト目がismbbtrail()でtrueであることも確認することにしました。
//		 */
//		if(ismbblead(p[0]) && ismbbtrail(p[1])) {
//			p += 2;
//		/* 半角文字ならば、変換する。終端だったら抜ける。 */
//		} else {
//			if(!(*p = tolower(*p))) break;
//			p += 1;
//		}
//	}
//	return string;
//}
//#else /*PIECE*/
//char* strupr(char* string);
//char* strlwr(char* string);
//asm("
//		.code
//		.align		1
//		.global		strupr
//		.global		strlwr
//strupr:
//		xld.w		%r13, toupper		;// %r13 := fn = toupper
//		jp		strupr_strlwr
//strlwr:
//		xld.w		%r13, tolower		;// %r13 := fn = tolower
//strupr_strlwr:
//		pushn		%r2
//		ld.w		%r0, %r12		;// %r0  := string
//		ld.w		%r1, %r12		;// %r1  := p
//		ld.w		%r2, %r13		;// %r2  := fn
//		;//-------------------------------------;//
//strupr_strlwr_LOOP:					;// for(;;) {
//		xld.b		%r12, [%r1+0]		;//   %r12 :=        p[0]
//		xcall		ismbblead		;//   if(!ismbblead (p[0])) goto SINGLE
//		cmp		%r10, 0
//		jreq		strupr_strlwr_SINGLE
//		;//
//		xld.b		%r12, [%r1+1]		;//   %r12 :=        p[1]
//		xcall		ismbbtrail		;//   if(!ismbbtrail(p[1])) goto SINGLE
//		cmp		%r10, 0
//		jreq		strupr_strlwr_SINGLE
//		;//
//		jp.d		strupr_strlwr_LOOP	;//   continue
//		add		%r1, 2			;//   p += 2	*delay*
//strupr_strlwr_SINGLE:
//		ld.b		%r12, [%r1]		;//   %r12 :=    *p
//		call		%r2			;//   %r10 := fn(*p)
//		ld.b		[%r1]+, %r10		;//   *p   := fn(*p)
//		;//
//		cmp		%r10, 0			;//   if(!*p) break
//		jrne		strupr_strlwr_LOOP
//		;//-------------------------------------;//
//strupr_strlwr_RET:					;// }
//		ld.w		%r10, %r0		;// %r10 := string
//		popn		%r2
//		ret
//");
//#endif /*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
///* * Thu Jul 09 00:22:54 JST 2009 Naoyuki Sawa
// * - VC++6.0の_splitpath()を、ほぼそのまま移植しました。
// */
//void
//splitpath(const char* path, char drive[/*MAX_DRIVE*/], char dir[/*MAX_DIR*/], char fname[/*MAX_FNAME*/], char ext[/*MAX_EXT*/])
//{
//	const char* sep;
//	const char* dot;
//	const char* p;
//	int len;
//
//	/* パス名が2文字以上で、2文字目が':'ならば、最初の2文字がドライブ名。 */
//	if((path[0] != '\0') && (path[1] == ':')) {
//		if(drive) {
//			memcpy(drive, path, 2);
//			drive[2] = '\0';
//		}
//		path += 2; /* pathをドライブ名の次へ進める */
//	} else {
//		if(drive) {
//			drive[0] = '\0';
//		}
//	}
//
//	/* 最後の区切り文字の次の位置と、最後のドット文字の位置を検索します。 */
//	sep = dot = NULL;
//	for(p = path; *p; p++) {
//		if((*p == '/') || (*p == '\\')) {
//			sep = p + 1; /* 区切り文字の次の位置 */
//		} else if(*p == '.') {
//			dot = p;     /* ドット文字の位置 */
//		}
//	}
//
//	/* 区切り文字が有れば、最後の区切り文字も含めてディレクトリ名を格納。 */
//	if(sep) {
//		if(dir) {
//			len = sep - path;
//			if(len > (MAX_DIR - 1)) {
//				len = (MAX_DIR - 1);
//			}
//			memcpy(dir, path, len);
//			dir[len] = '\0';
//		}
//		path = sep; /* pathをファイル名の先頭へ進める */
//	} else {
//		if(dir) {
//			dir[0] = '\0';
//		}
//	}
//
//	/* ファイル名の先頭よりも後ろに、ドット文字が有れば… */
//	if(dot >= path) { /* この条件で !dot も除外できます。 */
//		/* ドット文字の前まで、ファイル名として格納。 */
//		if(fname) {
//			len = dot - path;
//			if(len > (MAX_FNAME - 1)) {
//				len = (MAX_FNAME - 1);
//			}
//			memcpy(fname, path, len);
//			fname[len] = '\0';
//		}
//		/* ドット文字から最後まで、拡張子として格納。 */
//		if(ext) {
//			len = p/*末端*/ - dot;
//			if(len > (MAX_EXT - 1)) {
//				len = (MAX_EXT - 1);
//			}
//			memcpy(ext, dot, len);
//			ext[len] = '\0';
//		}
//	} else {
//		/* 残りの文字をすべて、ファイル名として格納。 */
//		if(fname) {
//			len = p/*末端*/ - path;
//			if(len > (MAX_FNAME - 1)) {
//				len = (MAX_FNAME - 1);
//			}
//			memcpy(fname, path, len);
//			fname[len] = '\0';
//		}
//		if(ext) {
//			ext[0] = '\0';
//		}
//	}
//}
//
///* * Thu Jul 09 00:22:54 JST 2009 Naoyuki Sawa
// * - VC++6.0の_makepath()を、少し変更して移植しました。
// *   VC++6.0の_makepath()は、path[MAX_PATH]への格納文字数をチェックしておらず、MAX_PATH文字を超えても格納してしまいます。
// *   以下のコードは、path[MAX_PATH]への格納文字数を、終端の'\0'を除いて(MAX_PATH-1)文字以下に切り詰めるよう変更しました。
// */
//void
//makepath(char path[/*MAX_PATH*/], const char* drive, const char* dir, const char* fname, const char* ext)
//{
//	char* path_end = &path[MAX_PATH - 1];
//
//	/* ドライブ名が1文字以上指定されていたら、ドライブ名を格納。 */
//	if(drive && *drive) {
//		*path++ = *drive;
//		//if(path == path_end) goto L_EXIT; /* 有り得ない */
//		*path++ = ':'; /* 2文字目は':'固定 */
//		//if(path == path_end) goto L_EXIT; /* 有り得ない */
//	}
//
//	/* ディレクトリ名が1文字以上指定されていたら、ディレクトリ名を格納。 */
//	if(dir && *dir) {
//		do {
//			*path++ = *dir++;
//			if(path == path_end) goto L_EXIT;
//		} while(*dir);
//		/* 末尾の区切り文字が省略されていたら、区切り文字を補う。
//		 * - ディレクトリ名に空文字列が指定された場合(dir="")は、区切り文字を補わないことに注意。
//		 *   もし空文字列(dir="")に対して区切り文字を補ってしまうと、以下のようなバグが発生する。
//		 *     splitpath("xxx.yyy", drive, dir, fname, ext); -> drive = "", dir = "", fname = "xxx", ext = ".yyy"
//		 *     makepath(path, drive, dir, fname, ext);       -> path = "\xxx.yyy" (元のpathに無い'\'が増えるバグ)
//		 */
//		if((*(path - 1) != '/') && (*(path - 1) != '\\')) {
//			*path++ = '\\';
//			if(path == path_end) goto L_EXIT;
//		}
//	}
//
//	/* ファイル名を格納。 */
//	if(fname) {
//		while(*fname) {
//			*path++ = *fname++;
//			if(path == path_end) goto L_EXIT;
//		}
//	}
//
//	/* 拡張子を格納。 */
//	if(ext) {
//		if(*ext != '.') {
//			*path++ = '.'; /* 先頭のドット文字が省略されていたら、ドット文字を補う */
//			if(path == path_end) goto L_EXIT;
//		}
//		while(*ext) {
//			*path++ = *ext++;
//			if(path == path_end) goto L_EXIT;
//		}
//	}
//L_EXIT:
//	*path = '\0'; /* 終端 */
//}
//
///*--------------------------------------------------------------------------*/
//
///* ワードの中で最初にセットされている最下位ビットの位置を返します。(Find first set)
// * [in]
// *	i	ワード
// * [out]
// *	戻り値	(i = ???????? ???????? ???????? ???????1 の時) ⇒  1 … 最下位ビットは 1
// *		(i = ???????? ???????? ???????? ??????10 の時) ⇒  2
// *		(i = ???????? ???????? ???????? ?????100 の時) ⇒  3
// *		                  ( 中 略 )
// *		(i = ?1000000 00000000 00000000 00000000 の時) ⇒ 31
// *		(i = 10000000 00000000 00000000 00000000 の時) ⇒ 32 … 最上位ビットは32
// *		(i = 00000000 00000000 00000000 00000000 の時) ⇒  0 … ワードが0ならば0
// * [note]
// *	- 本関数は、ffs(): 4.3BSD, POSIX.1-2001. 準拠です。
// *	  参照: Linux Programmer's Manual (3) FFS
// *	        http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/ffs.3.html
// *	- P/ECEのコンパイラgcc33は、__builtin_ffsを認識しますが、インライン展開しません。
// *	  gcc33は、__builtin_ffsを見つけると、ffs関数を呼び出すコードを生成するようです。
// *	  つまり、プログラム中で__builtin_ffsを使うのとffsを使うのは、等価です。
// */
//#ifndef PIECE
////----------------------------------------------------------------------------
////C言語版
////----------------------------------------------------------------------------
//int ffs(int i) {
//	int n;
//	for(n = 1; n <= 32; n++) {
//		if((i & 1) != 0) {
//			return n;
//		}
//		i >>= 1;
//	}
//	return 0;
//}
//#else /*PIECE*/
////----------------------------------------------------------------------------
////アセンブラ版①
////高速ですが、ややコードサイズが大きな実装です。
////----------------------------------------------------------------------------
////asm("
////		.code
////		.align		1
////		.global		ffs
////ffs:
////		swap		%r12, %r12	;//          +-- 反転後の値で､1が最上位ビットに有る時                                             反転後の値で､1が最下位ビットに有る時 --+   +-- 1が無い時
////		mirror		%r12, %r12	;//          |                                                                                                                           |   |
////		scan1		%r10, %r12	;// %r10 :=  0   1   2   3   4   5   6   7   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8
////		sll		%r12, %r10
////		scan1		%r11, %r12	;// %r11 :=  0   0   0   0   0   0   0   0   0   1   2   3   4   5   6   7   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8   8
////		sll		%r12, %r11
////		add		%r10, %r11	;// %r10 :=  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  16  16  16  16  16  16  16  16  16  16  16  16  16  16  16  16
////		scan1		%r11, %r12	;// %r11 :=  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   1   2   3   4   5   6   7   8   8   8   8   8   8   8   8   8
////		sll		%r12, %r11
////		add		%r10, %r11	;// %r10 :=  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  24  24  24  24  24  24  24  24
////		scan1		%r11, %r12	;// %r11 :=  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   1   2   3   4   5   6   7   8
////		jruge.d		3
////		 add		%r10, %r11	;// %r10 :=  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32	*delay*
////		 ld.w		%r10, -1
////		ret.d
////		add		%r10, 1		;// %r10 :=  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32   0
////");
////----------------------------------------------------------------------------
////アセンブラ版②
////低速で、省メモリな実装です。
////ffs()は使用頻度が低く、実行速度は重要でないので、こちらの方が良いです。
////----------------------------------------------------------------------------
////asm("
////		.code
////		.align		1
////		.global		ffs
////ffs:
////		cmp		%r12, 0		;// if(i != 0) {
////		jreq.d		7
////		 ld.w		%r10, 0		;// %r10 := n = 0						*delay*
////		 swap		%r12, %r12	;//   %r12 := i = 反転(i)
////		 mirror		%r12, %r12
////		  add		%r12, %r12	;//   do { %psr(C) := i[31], %r12 := i <<= 1, %r10 := n++
////		 jruge.d	-1		;//   } while(%psr(C) == 0)
////		 add		%r10, 1		;// }								*delay*
////		ret				;// return n
////");
////----------------------------------------------------------------------------
////アセンブラ版③
////さらに低速で、さらに省メモリな実装です。
////処理方法を工夫して、一命令節約しました。
////----------------------------------------------------------------------------
//asm("
//		.code
//		.align		1
//		.global		ffs
//ffs:
//		cmp		%r12, 0		;// if(i != 0) {
//		jreq.d		6
//		 ld.w		%r10, 0		;// %r10 := n = 0			*delay*
//		  sll		%r12, 1		;//   do { %r12 := i <<= 1, %r10 := n++
//		 jrne.d		-1		;//   } while(i != 0)
//		 sub		%r10, 1		;//					*delay*
//		 add		%r10, 33	;// %r10 := n += 33  (-1～-32)⇒(32～1)
//		ret				;// return n
//");
//#endif/*PIECE*/
//
///*--------------------------------------------------------------------------*/
//
////{{2014/09/29追加:malloc_usable_size()を追加しました。
//size_t malloc_usable_size(void* ptr) {
//	//                              |               |
//	//  (((HEAPMEM*)ptr)-1)         +===============+
//	//                              | mark  | owner |
//	//                              +---------------|
//	//                              |     chain     |
//	//              ptr             +---------------+
//	//               A              |///////////////|
//	//               |              |///////////////|
//	//               |size          |///////////////|
//	//               |              |///////////////|
//	//               V              |///////////////|
//	// ((((HEAPMEM*)ptr)-1)->chain) +===============+
//	//                              | mark  | owner |
//	//                              +---------------|
//	//                              |     chain     |
//	//                              +---------------+
//	//                              |               |
//	return ptr ? ((int)((((HEAPMEM*)ptr)-1)->chain) - (int)ptr) : 0;
//}
////}}2014/09/29追加:malloc_usable_size()を追加しました。
//}}2015/01/07削除:「標準Ｃライブラリの補助」の実装を、clippce.c⇒cliplibc.cへ移動しました。

/****************************************************************************
 *	下位レベル関数 : 64ビット整数演算ライブラリ
 ****************************************************************************/

/* * Sun Feb  6 06:09:00 JST 2005 Naoyuki Sawa
 * - 64ビット整数演算ライブラリは、きわめて使用頻度が低いので、
 *   不要なアプリケーションにリンクされてサイズが無駄に増えるのを防ぐために、
 *   clippce.c -> clipdi.cへ移しました。
 */

/****************************************************************************
 *	コアCPU制御 (s1c33cpu.hへの追加)
 ****************************************************************************/

//{{2014/08/16削除:get_psr(),set_psr()をmachine.hで実装し、当モジュールの実装は削除しました。
///* %psrレジスタを取得します。
// *
// *	int get_psr()
// */
//asm("
//	.code
//	.align	1
//	.global	get_psr
//get_psr:
//	ret.d
//	ld.w	%r10, %psr	; *delay*
//");
///* %psrレジスタを設定します。
// *
// *	void set_psr(int psr)
// */
//asm("
//	.code
//	.align	1
//	.global	set_psr
//set_psr:
//	ret.d
//	ld.w	%psr, %r12	; *delay*
//");
//}}2014/08/16削除:get_psr(),set_psr()をmachine.hで実装し、当モジュールの実装は削除しました。

/*****************************************************************************
 *	ユーティリティ：タイマ
 *****************************************************************************/

//{{2015/11/24削除:unistd.h,cliplibc.cにてPOSIX仕様のusleep()を実装した事に伴い、既存のclippce.h,clippce.cの独自仕様のusleep()を削除しました。
//void
//usleep(unsigned long usec)
//{
//	unsigned long st, ed;
//
//	/* 注意!pceTimerGetPrecisionCount()の戻り値はリニアな値ではありません。
//	 * 差分を求めるにはpceTimerAdjustPrecisionCount()を使う必要があります。
//	 */
//	st = pceTimerGetPrecisionCount();
//	do {
//		ed = pceTimerGetPrecisionCount();
//	} while(pceTimerAdjustPrecisionCount(st, ed) < usec);
//}
//}}2015/11/24削除:unistd.h,cliplibc.cにてPOSIX仕様のusleep()を実装した事に伴い、既存のclippce.h,clippce.cの独自仕様のusleep()を削除しました。

/*--------------------------------------------------------------------------*/

void pdwait(int n);
asm("
		.code
		.align		1
		.global		pdwait
pdwait:
	;//{{3命令⇒2命令に節約
	;//	xld.w		%r13, 0x01000000	;//エリア11～12(7ウェイト,3.5サイクル,8ビット)
	;//↓3命令⇒2命令に節約
		ld.w		%r13, 1			;//%r13 := 0x00000001
		rr		%r13, 8			;//%r13 := 0x01000000
	;//}}3命令⇒2命令に節約
		ld.w		%r14, %r13		;//%r14 := 0x01000000
		mac		%r12
		ret
");

/*****************************************************************************
 *	ユーティリティ：描画
 *****************************************************************************/

//{{2003/05/01:BIOS1.20で修正されました。もう不要です。
//void
//lcd_init()
//{
//	unsigned char buf[100/*三倍ぐらい余裕取ってます*/], *p, cmd;
//	int len, i;
//
//	/* あらかじめ初期化コマンドを作成しておきます。
//	 * * S6B0741データシート(DS_S6B0741_V21.pdf)の56ページ
//	 *   「Referential Instruction Set-up Flow:
//	 *     Initializing with the built-in Power Supply Circuits」
//	 *   を参照してください。
//	 * * 各設定値は、現在のP/ECEカーネル(1.18)に合わせてあります。
//	 * * 姉妹品(?)LCD「OGM-128GB21T-S-KD010」の推奨設定値とは異なります。
//	 *   OGM-128GB21T-S-KD010データシート(128GB21T-S-KD010.pdf)を参照してください。
//	 * * 128GB21T-S-KD010.pdfの4ページ「1. FEATURES」に書かれている推奨設定値では：
//	 *	Driving method ... 1/128 duty, 1/12 bias
//	 *   となっており、P/ECEカーネルでの設定値：
//	 *	Driving method ... 1/128 duty, 1/11 bias
//	 *    とは少し異なります。
//	 *   （P/ECEカーネルも昔は1/12だった形跡があり、コメントが1/12のままです）
//	 * * 同じく、10ページ「9. PROGRAM SETTING VALUE」に書かれている推奨設定値では：
//	 *	BLACK     : 9990
//	 *	DARK GRAY : 9900
//	 *	LIGHT GRAY: 9000
//	 *	WHITE     : 0000
//	 *   となっており、P/ECEカーネルでの設定値：
//	 *	BLACK     : 8880
//	 *	DARK GRAY : 8800
//	 *	LIGHT GRAY: 8000
//	 *	WHITE     : 0000
//	 *   とは少し異なります。
//	 * * 試しにbias値とパレットをOGM-128GB21T-S-KD010の推奨設定値に合わせてみると、
//	 *   OGM-128GB21T-S-KD010の推奨設定値でも充分きれいに見えることがわかりました。
//	 *   bias値で薄い方へ調整され、パレットで濃い方へ調整されるので、
//	 *   打ち消しあって、結局、元と同じような濃度になるのだと思います。
//	 * * P/ECEカーネル・OGM-128GB21T-S-KD010どちらに合わせても(少なくとも見た目は)
//	 *   いいと思うのですが、安全のために、P/ECEカーネルに合わせることにしました。
//	 */
//	p = buf;
//	/* User Application Setup by Internal Instructions */
//	/* [Display Duty Select] */
//	*p++ = 0x48; *p++ = 128;
//	/* [ADC Select] */
//	*p++ = 0xa0 + 0;
//	/* [SHL Select] */
//	*p++ = 0xc0 + 8;
//	/* [COM0 Register Select] */
//	*p++ = 0x44; *p++ = 0;
//	/* User LCD Power Setup by Internal Instructions */
//	/* [Oscillator ON] */
//	*p++ = 0xab;
//	/* [DC-DC Step-up Register Select] */
//	*p++ = 0x64 + 2;
//	/* [Regulator Resistor Select] */
//	*p++ = 0x20 + 6;
//	/* [Electronic Volume Register Select] */
//	*p++ = 0x81; *p++ = pceLCDSetBright(INVALIDVAL);
//	/* [LCD Bias Register Select] */
//	*p++ = 0x50 + 6;
//	/* [Gray-scale Select] */
//	*p++ = 0x94;			/* Set FRC and PWM mode */
//	*p++ = 0x88 + 0; *p++ = 0x00;	/* Set white mode and 1/2 frame */
//	*p++ = 0x88 + 1; *p++ = 0x00;	/* Set white mode and 3/4 frame */
//	*p++ = 0x88 + 2; *p++ = 0x08;	/* Set light gray mode and 1/2 frame */
//	*p++ = 0x88 + 3; *p++ = 0x00;	/* Set light gray mode and 3/4 frame */
//	*p++ = 0x88 + 4; *p++ = 0x88;	/* Set dark gray mode and 1/2 frame */
//	*p++ = 0x88 + 5; *p++ = 0x00;	/* Set dark gray mode and 3/4 frame */
//	*p++ = 0x88 + 6; *p++ = 0x88;	/* Set black mode and 1/2 frame */
//	*p++ = 0x88 + 7; *p++ = 0x08;	/* Set black mode and 3/4 frame */
//	/* (リセットフローに載っていないもの) */
//	*p++ = 0x4c; *p++ = 0x1f;	/* Set N-line inversion */
//	*p++ = 0x40; *p++ = 80;		/* Set initial display line register */
//	/* [Power Control] */
//	*p++ = 0x28 + 7;
//	len = p - buf; /* コマンドバイト数 */
//
//	/* LCDC制御を入出力ポート経由に切り替えます。
//	 * * 現在のP/ECEカーネル(1.18)は、P1x系列の入出力方向を設定していません。
//	 *   リセット後の初期状態である「IN」のままになっています。
//	 * * P32,15,16を入出力ポートに切り替える前に入出力方向を設定しておかないと、
//	 *   入力同士でぶつかってゴミが送信されてしまうことがあります。
//	 * * また、P32,15,16を入出力ポートに切り替える前にSCLKを0にしておかないと、
//	 *   いきなりSCLKが1になってゴミが送信されてしまうことがあります。
//	 *   念のため、SIDも0にしておくことにします。
//	 * * Sun Oct 20 21:37:00 JST 2002 Naoyuki Sawa
//	 *   c33209.hにポートSIO機能拡張レジスタ(0x402d7)の定義を追加しました。
//	 *   このポートはたぶんS1C33208→S1C33209の時に拡張されたものだと思うので、
//	 *   オリジナルのc33208.hには載っていませんでした。
//	 *   ポートSIO機能拡張レジスタの定義を追加したので、
//	 *   これまでポートアドレスを直接指定してP16/P15/P32の設定を行っていた部分を、
//	 *   bPSIO3_CFEX_*を使うように変更しました。
//	 */
//	bP3_IOC3_IOC32 = 0;	/* CSBの戻り  -> IN  (そのまま) */
//	bP1_IOC1_IOC15 = 1;	/* SCLK       -> OUT (※必須！) */
//	bP1_IOC1_IOC16 = 1;	/* SID        -> OUT (※必須！) */
//	bP3_IOC3_IOC33 = 1;	/* RESETB     -> OUT (そのまま) */
//	bP1_P1D_P15D = 0;	/* SCLK出力値 -> 0   (※必須！) */
//	bP1_P1D_P16D = 0;	/* SID 出力値 -> 0   (念のため) */
//	bPSIO3_CFEX_SSOUT3 = 0;	/* P16        -> P16            */
//	bPSIO3_CFEX_SSCLK3 = 0;	/* P15        -> P15            */
//	bPSIO3_CFEX_SSRDY3 = 0;	/* P32        -> P32            */
//
//	/* LCDCをリセットします。
//	 * * S6B0741データシート(DS_S6B0741_V21.pdf)の67ページ
//	 *   「Reset Input Timing」を参照してください。
//	 *   （実際には、P/ECEのCPU速度(24MHz)なら、ぜんぜん待たなくても大丈夫です）
//	 */
//	bP3_P3D_P33D = 0;	/* RESETB -> リセット */
//	usleep(1 + 1/*誤差*/);	/* 少なくとも1000[ns] */
//	bP3_P3D_P33D = 1;	/* RESETB -> 解除     */
//	usleep(1 + 1/*誤差*/);	/* 少なくとも1000[ns] */
//
//	/* 初期化コマンドを発行します。 */
//	bP2_P2D_P21D = 0;	/* RS  -> コマンド */
//	bP2_P2D_P20D = 0;	/* CSB -> 選択     */
//	p = buf;
//	while(len-- != 0) {
//		cmd = *p++;
//		/* ビット7～0の順で送出。
//		 * * 待ち時間については、S6B0741データシート(DS_S6B0741_V21.pdf)の66ページ
//		 *   「Serial Interface Characteristics」を参照してください。
//		 *   （実際には、P/ECEのCPU速度(24MHz)なら、ぜんぜん待たなくても大丈夫です）
//		 */
//		for(i = 0; i < 8; i++, cmd <<= 1) {
//			bP1_P1D_P15D = 0;
//			bP1_P1D_P16D = cmd & 0x80 ? 1 : 0;
//			usleep(1 + 1/*誤差*/);	/* 少なくとも30[ns] */
//			bP1_P1D_P15D = 1;
//			usleep(1 + 1/*誤差*/);	/* 少なくとも30[ns] */
//		}
//	}
//	bP2_P2D_P20D = 1;	/* CSB -> 解除 */
//
//	/* [Power Control]後の Waiting for Stabilizing the LCD Power Levels は、
//	 * データシートに待ち時間が規定されていないので、念のため長めに待ちます。
//	 * （実際には、P/ECEのCPU速度(24MHz)なら、ぜんぜん待たなくても大丈夫です）
//	 */
//	usleep(1000);		/* 1ミリ秒待てば充分かな？ */
//
//	/* LCDC制御をシリアルインターフェイス経由に戻します。
//	 * 入出力方向は関係ないので、元に戻す必要はありません。
//	 */
//	bPSIO3_CFEX_SSOUT3 = 1;	/* P16        ->  SSOUT3 */
//	bPSIO3_CFEX_SSCLK3 = 1;	/* P15        -> #SSCLK3 */
//	bPSIO3_CFEX_SSRDY3 = 1;	/* P32        -> #SSRDY3 */
//
//	/* RESETB端子によるリセット後は、Display ON/OFF:OFF になっています。
//	 * このあと必要に応じて、pceLCDDispStart()を呼び出してください。
//	 * （ちなみにResetコマンドの場合は、Display ON/OFF状態を維持します）
//	 */
//}
//}}2003/05/01:BIOS1.20で修正されました。もう不要です。

//{{2006/12/05:render_object()が定着し、draw_object()は使わなくなったので、削除しました。
//void
//draw_object(PIECE_BMP* src, int dx, int dy, int sx, int sy, int w, int h, int param)
//{
//	DRAW_OBJECT obj;
//
//	pceLCDSetObject(&obj, src, dx, dy, sx, sy, w, h, param);
//	pceLCDDrawObject(obj);
//}
//}}2006/12/05:render_object()が定着し、draw_object()は使わなくなったので、削除しました。

//static void _render_object(RENDER* render, int dx, int dy, int sx, int sy, int w, int h, int param) __attribute__((unused))/*asmブロックから参照*/;
//↓2007/09/27 ASM化に伴う警告(warning: `_render_object' declared `static' but never defined)を抑制するため宣言変更。実際にはローカルのままです。
void _render_object(RENDER* render, int dx, int dy, int sx, int sy, int w, int h, int param);
static void _render_mask(RENDER* render, int dx, int dy, int sx, int sy, int w, int h, int param) __attribute__((unused))/*asmブロックから参照*/;

#if 0
//void
//render_object(RENDER* render, int dx, int dy, int sx, int sy, int w, int h, int param)
//{
//	if(!(param & DRW_MASK(0))) {
//		_render_object(render, dx, dy, sx, sy, w, h, param);
//	} else {
//		_render_mask  (render, dx, dy, sx, sy, w, h, param);
//	}
//}
#else	//↓高速化
asm("
	.code
	.align	1
	.global	render_object
render_object:
	;  %r12    = render
	;  %r13    =     dx
	;  %r14    =     dy
	;  %r15    =     sx
	; [%sp+ 0] = (retp)( 7: 0)
	; [%sp+ 1] = (retp)(15: 8)
	; [%sp+ 2] = (retp)(23:16)
	; [%sp+ 3] = (retp)(31:24)
	; [%sp+ 4] =     sy( 7: 0)
	; [%sp+ 5] =     sy(15: 8)
	; [%sp+ 6] =     sy(23:16)
	; [%sp+ 7] =     sy(31:24)
	; [%sp+ 8] =      w( 7: 0)
	; [%sp+ 9] =      w(15: 8)
	; [%sp+10] =      w(23:16)
	; [%sp+11] =      w(31:24)
	; [%sp+12] =      h( 7: 0)
	; [%sp+13] =      h(15: 8)
	; [%sp+14] =      h(23:16)
	; [%sp+15] =      h(31:24)
	; [%sp+16] =  param( 7: 0)
	; [%sp+17] =  param(15: 8)
	; [%sp+18] =  param(23:16)
	; [%sp+19] =  param(31:24)
	xbtst	[%sp+17],2
	xjreq	_render_object
	xjp	_render_mask
");
#endif

#if 0	/*{{2007/09/27まで安定して動作していたコードなので、コメントアウトして残しておきます。*/
//static void
//_render_object(RENDER* render, int dx, int dy, int sx, int sy, int w, int h, int param)
//{
//	DRAW_OBJECT obj;
//	PIECE_VRAM* vram = (PIECE_VRAM*)render->surface;
//	PIECE_BMP* pbmp = render->context->texture;
//	if(pbmp == NULL) DIE(); /* テクスチャ指定必須 */
//
//	/* 転送先設定。 */
//	obj.dest = vram;
//	obj.dx = dx;
//	obj.dy = dy;
//	obj.dw = w;
//	obj.dh = h;
//
//	/* 転送元設定。 */
//	obj.src = pbmp;
//	obj.sx = sx;
//	obj.sy = sy;
//	obj.clip.left = render->left;
//	obj.clip.top = render->top;
//	obj.clip.right = render->right;
//	obj.clip.bottom = render->bottom;
//
//	/* その他設定。 */
//	obj.param = param;
//	obj.type = TYPE_BMP;
//
//	/* 描画。 */
//	pceLCDDrawObject(obj);
//}
#endif	/*}}2007/09/27まで安定して動作していたコードなので、コメントアウトして残しておきます。*/
//↓ * Thu Sep 27 15:54:04 JST 2007 Naoyuki Sawa
//↓ - _render_object()をASM化して、高速化しました。
//↓   C言語版では、「pceLCDDrawObject(obj)」呼び出し時に、無駄なmemcpy()が生成されてしまっていました。
//↓   2007/09/27 ASM版の主な高速化は、上述の無駄なmemcpy()を省いたことです。
/*{{2007/09/27 ASM版*/
static void _render_object_ERR() __attribute__((unused))/*asmブロックから参照*/;
static void _render_object_ERR() { DIE(); }
asm("
	.code
	.align	1
_render_object:
	;// %r12     = render
	;// %r13     = dx
	;// %r14     = dy
	;// %r15     = sx
	;// [%sp+ 4] = sy
	;// [%sp+ 8] = w
	;// [%sp+12] = h
	;// [%sp+16] = param
	ld.w	%r4, [%r12]+		; %r4 = render->surface
	ld.h	%r5, [%r12]+		; %r5 = render->left
	ld.h	%r6, [%r12]+		; %r6 = render->top
	ld.h	%r7, [%r12]+		; %r7 = render->right
;//{{高速化
;//	ld.h	%r10, [%r12]+		; %r10 = render->bottom
;//	xld.w	%r11, [%r12+8]		; %r11 = render->context
;//↓「ld.h %rd,[%rb]+」(2サイクル)を「ld.h %rd,[%rb]」(1サイクル)に変更し、1サイクル分高速化しました。
;//↓後続の「xld.w %rd,[%rb+imm]」において、%rbとimmが個別にwordアライメントされていなくても、
;//↓(%rb+imm)の実効アドレスがwordアライメントされていれば正しく動作する性質を利用しています。
	ld.h	%r10, [%r12]		; %r10 = render->bottom
	xld.w	%r11, [%r12+10]		; %r11 = render->context
;//}}高速化
	xld.w	%r11, [%r11+48]		; %r11 = render->context->texture
	xcmp	%r11, 0
	xjreq	_render_object_ERR	; テクスチャ指定必須
	;// DRAW_OBJECT {
	;// 	PIECE_VRAM*	dest;	; + 0,4
	;// 	short		dx;	; + 4,2
	;// 	short		dy;	; + 6,2
	;// 	short		dw;	; + 8,2
	;// 	short		dh;	; +10,2
	;// 	PIECE_BMP*	src;	; +12,4
	;// 	short		sx;	; +16,2
	;// 	short		sy;	; +18,2
	;// 	RECTP {
	;//	 unsigned char	left;	; +20,1
	;//	 unsigned char	top;	; +21,1
	;//	 unsigned char	right;	; +22,1
	;//	 unsigned char	bottom;	; +23,1
	;//	} clip;
	;// 	BYTE		disp;	; +24,1
	;// 	BYTE		param;	; +25,1
	;// 	BYTE		type;	; +26,1
	;// 	BYTE		layer;	; +27,1
	;// };				; =28
	xsub	%sp, %sp, 28
	;// %r4      = render->surface
	;// %r5      = render->left
	;// %r6      = render->top
	;// %r7      = render->right
	;// %r10     = render->bottom
	;// %r11     = render->context->texture
	;// %r13     = dx
	;// %r14     = dy
	;// %r15     = sx
	;// [%sp+ 0] = obj
	;// [%sp+32] = sy
	;// [%sp+36] = w
	;// [%sp+40] = h
	;// [%sp+44] = param
	;//--- 転送先設定 ---
	xld.w	[%sp+ 0], %r4		; obj.dest = render->surface
	xld.h	[%sp+ 4], %r13		; obj.dx = dx
	xld.h	[%sp+ 6], %r14		; obj.dx = dy
	xld.h	%r4,  [%sp+32]		; %r4 = sy
	xld.h	%r12, [%sp+36]		; %r12 = w
	xld.h	%r13, [%sp+40]		; %r13 = h
	xld.ub	%r14, [%sp+44]		; %r14 = param (bit8よりも上の独自拡張部分は取得せずに、確実に0とします)
	xld.h	[%sp+ 8], %r12		; obj.dw = w
	xld.h	[%sp+10], %r13		; obj.dh = h
	;// %r4     = sy
	;// %r5     = render->left
	;// %r6     = render->top
	;// %r7     = render->right
	;// %r10    = render->bottom
	;// %r11    = render->context->texture
	;// %r14    = param
	;// %r15    = sx
	;// [%sp+0] = obj
	;//--- 転送元設定 ---
	xld.w	[%sp+12], %r11		; obj.src = render->context->texture
	xld.h	[%sp+16], %r15		; obj.sx = sx
	xld.h	[%sp+18], %r4		; obj.sy = sy
	xld.b	[%sp+20], %r5		; obj.clip.left = render->left
	xld.b	[%sp+21], %r6		; obj.clip.top = render->top
	xld.b	[%sp+22], %r7		; obj.clip.right = render->right
	xld.b	[%sp+23], %r10		; obj.clip.bottom = render->bottom
	;// %r14    = param
	;// [%sp+0] = obj
	;//--- その他設定 ---
	xsll	%r14, 8			; %r14 = (0<<24)|(0<<16)|(param<<8)|0
	xld.w	[%sp+24], %r14		; obj.disp = 0(=dummy), obj.param = param, obj.type = 0(=TYPE_BMP), obj.layer = 0(=dummy)
	;// [%sp+0] = obj
	;//--- 描画 ---
	xcall	pceLCDDrawObject	; pceLCDDrawObject(obj) (objは5byte以上の構造体なので、スタック渡しです)
	xadd	%sp, %sp, 28
	ret
");
/*}}2007/09/27 ASM版*/

static void
_render_mask(RENDER* render, int dx, int dy, int sx, int sy, int w, int h, int param)
{
	PIECE_BMP* pbmp;
	int color;

	pbmp = render->context->texture;
	if(!pbmp) DIE();

	color = (param >> 8) & 3;
	param = (unsigned char)param;

	/* 1bit */
	if(pbmp->header.bpp == 1) {
		int c1 = (param >> 0) & 7; /* DRW_CLR(C1) */
		int c2 = (param >> 3) & 7; /* DRW_CLR(C2) */
		if(c1 != COLOR_MASK) c1 = color; /* 1のビットの色指定が透明でなければ単一色に置換 */
		if(c2 != COLOR_MASK) c2 = color; /* 0のビットの色指定が透明でなければ単一色に置換 */
		_render_object(render, dx, dy, sx, sy, w, h, (param & DRW_REV) | DRW_CLR(c1, c2));

	/* 2bitマスク付き or 4bitマスク付き */
	} else if(pbmp->header.mask == 1) {
		PIECE_BMP tmp = *pbmp; /* マスクデータを1bitデータとして利用 */
		tmp.header.bpp  = 1;
		tmp.header.mask = 0;
		tmp.buf  = tmp.mask;
		render->context->texture = &tmp; /* 一時的にテクスチャを置換 */
		_render_object(render, dx, dy, sx, sy, w, h, (param & DRW_REV) | DRW_CLR(color, COLOR_MASK));
		render->context->texture = pbmp; /* 置換したテクスチャを戻す */

	/* 2bitマスク無し or 4bitマスク無し */
	} else {
		render_rectangle_fill(render, dx, dy, w, h, color);
	}
}

/****************************************************************************
 *	ユーティリティ：フォント
 ****************************************************************************/

/*
#############
SJIS⇔JIS変換
#############

==============
JISX0208⇒SJIS
==============

コードから0x2121を引く
(コード&0x100)が0でなければコードに0x9E,0ならばコードに0x40を足す
下位バイトが0x7F以上ならばコードに1を足す
[上位バイトだけの処理]上位バイトを右に１ビットシフトし、それに0x81を足す
(0xA000<=コード)ならばコードに0x4000を足す

==============
SJIS⇒JISX0208
==============

(0xE000<=コード)ならばコードから0x4000を引く
[上位バイトのみ]上位バイトから0x81を引き１ビット左にシフトする
下位バイトが0x80以上ならコードから１を引く
下位バイトが0x9E以上ならコードに0x100を足し、コードから0x9Eを引く。そうでなければコードから0x40を引く
コードに0x2121を足す。

「コードに0x100を足し、コードから0x9Eを引く」
というのはつまり，
「上位バイトに１を足し，下位バイトから0x9Eを引く」
であり，
「コード全体に0x62を足す」
ことでもあります．

-----------------------------------------------------------------------------
『漢字コードまめ知識』より
(http://www.d2.dion.ne.jp/~imady/kcode/kcodemame.html)
*/

/* 各フォントタイプの幅と高さ。clipfram.c内のrender_font()からも参照されます。 */
//{{2015/05/26変更:4x5ピクセルフォントを追加しました。
//const unsigned char render_font_size[3][2] = { {5,10}, {8,16}, {4,6} };
//↓2015/05/26変更:4x5ピクセルフォントを追加しました。
const unsigned char render_font_size[4][2] = { {5,10}, {8,16}, {4,6}, {4,5} };
//}}2015/05/26変更:4x5ピクセルフォントを追加しました。

/* 縁取りフォント描画のパラメータテーブル。render_font_framed()などから参照します。 */
const RENDER_FONT_FRAMED_PARAM render_font_framed_param[9] = {
	{ 0, 0, 8 }, { 2, 0, 8 }, { 0, 2, 8 }, { 2, 2, 8 }, /* 縁(角の部分) */
	{ 1, 0, 4 }, { 0, 1, 4 }, { 2, 1, 4 }, { 1, 2, 4 }, /* 縁(直線部分) */
	{ 1, 1, 0 },                                        /* 中抜き */
};

/* 2002/11/19: 高速化のために、render_font()はclipfram.cに移しました。 */

/* シンプルライブラリのsymbol()関数(\usr\PIECE\lib\src\simple\symbol.c)を参考にしました。 */
unsigned int
render_string(RENDER* render, int x, int y, const char* str, int type, int color)
{
	int ml, /*w,*/ h, xmin, ymin, xmax, ymax;
	unsigned char c1, c2;
	unsigned short code, wh;
//{{2009/06/26:CR/LF/CR+LF対応のため追加
	unsigned char crlf_sel = 0; /* 改行ありの場合、CRまたはLFの先に現れた方を格納する */
//}}2009/06/26:CR/LF/CR+LF対応のため追加

	/* 改行の有無とフォントサイズを求めます。 */
	ml = !(type & 0x80);
	type &= 0x7f;
    //{{2015/05/26変更:4x5ピクセルフォントを追加しました。
    //	if(type < 0 || 2 < type) DIE(); /* 無効なフォントタイプ */
    //↓2015/05/26変更:4x5ピクセルフォントを追加しました。
	if((unsigned)type > 3) { DIE(); }//無効なフォントタイプ
    //}}2015/05/26変更:4x5ピクセルフォントを追加しました。
	/*w = render_font_size[type][0];*/
	h = render_font_size[type][1]; /* 改行時のみ利用 */

	/* 描画範囲を求めるために、まず空矩形に設定。 */
	xmin = xmax = x;
	ymin = ymax = y;

	/* 各文字を描画… */
	while((c1 = *str++) != '\0') {
		/* 全角 */
		if((0x81 <= c1 && c1 <= 0x9f) || (0xe0 <= c1 && c1 <= 0xfc)) {
			if((c2 = *str++) == '\0') break; /* 全角が途中で切れてる。不正な文字列 */
			code = c1 << 8 | c2;
		/* 半角 */
		} else {
//{{2009/06/26:CR/LF/CR+LF対応のため変更
//			if(ml && c1 == '\n') { /* 改行ありなら、改行コードを処理 */
//				x = xmin;
//				y += h;
//				continue;
//			}
//↓2009/06/26:CR/LF/CR+LF対応のため変更
			if(ml) {					/* 改行ありなら… */
				if((c1 == '\r') || (c1 == '\n')) {	/* CRまたはLFなら… */
					if(!crlf_sel) {			/* 先に現れた方を… */
						crlf_sel = c1;		/* 改行コードとして選択 */
					}
					if(c1 == crlf_sel) {		/* 選択したコードなら… */
						x = xmin;		/* 改行し表示はしない */
						y += h;
					} else {			/* 選択したのと逆なら… */
						/** no job */		/* 改行も表示もしない */
					}
					continue;
				}
			}
//}}2009/06/26:CR/LF/CR+LF対応のため変更
			code = c1;
		}

		/* 文字を描きます。 */
		wh = render_font(render, x, y, code, type, color);
		x += wh & 0xff; /* 次の描画位置を右へ進める */

		/* 描画範囲を更新します。
		 * 実際に描いてから更新します！従って、文字列末尾の改行は描画範囲に影響しません。
		 */
		if(x             > xmax) xmax = x            ;
		if(y + (wh >> 8) > ymax) ymax = y + (wh >> 8);
	}

	return (ymax - ymin) << 16 | (xmax - xmin);
}

/* 文字列展開バッファとして_def_vbuff[]を利用しています。 */
unsigned int
render_printf(RENDER* render, int x, int y, int type, int color, const char* fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vsprintf(_def_vbuff, fmt, ap);
	va_end(ap);

	return render_string(render, x, y, _def_vbuff, type, color);
}

unsigned short
render_font_framed(RENDER* render, int x, int y, unsigned short code, int type, int color)
{
//{{2009/07/15:コードサイズ低減のため、render_font_framed_param[]を使うように変更
//	int c;
//	unsigned short wh;
//
//	/* 縁(角の部分) */
//	c = color >> 8 & 0xf;
//	if((wh = render_font(render, x + 0, y + 0, code, type, c)) == 0) return 0;
//	         render_font(render, x + 2, y + 0, code, type, c);
//	         render_font(render, x + 0, y + 2, code, type, c);
//	         render_font(render, x + 2, y + 2, code, type, c);
//
//	/* 縁(直線部分) */
//	c = color >> 4 & 0xf;
//	render_font(render, x + 1, y + 0, code, type, c);
//	render_font(render, x + 0, y + 1, code, type, c);
//	render_font(render, x + 2, y + 1, code, type, c);
//	render_font(render, x + 1, y + 2, code, type, c);
//
//	/* 中抜き */
//	c = color >> 0 & 0xf;
//	render_font(render, x + 1, y + 1, code, type, c);
//
//	return wh + 0x0202; /* 縦横2ピクセルづつ増えます */
//↓2009/07/15:コードサイズ低減のため、render_font_framed_param[]を使うように変更
	int wh;
	int i;

	for(i = 0; i < ARRAY_SIZE(render_font_framed_param); i++) {
		wh = render_font(render,
			x + render_font_framed_param[i].x,
			y + render_font_framed_param[i].y,
			code,
			type,
			(color >> render_font_framed_param[i].c) & 15);
		if(!wh) {
			return 0;
		}
	}

	return wh + 0x0202; /* 縦横2ピクセルづつ増える */
//}}2009/07/15:コードサイズ低減のため、render_font_framed_param[]を使うように変更
}

unsigned int
render_string_framed(RENDER* render, int x, int y, const char* str, int type, int color)
{
//{{2009/07/15:コードサイズ低減のため、render_font_framed_param[]を使うように変更
//	int c;
//	unsigned int wh;
//
//	/* 縁(角の部分) */
//	c = color >> 8 & 0xf;
//	if((wh = render_string(render, x + 0, y + 0, str, type, c)) == 0) return 0;
//	         render_string(render, x + 2, y + 0, str, type, c);
//	         render_string(render, x + 0, y + 2, str, type, c);
//	         render_string(render, x + 2, y + 2, str, type, c);
//
//	/* 縁(直線部分) */
//	c = color >> 4 & 0xf;
//	render_string(render, x + 1, y + 0, str, type, c);
//	render_string(render, x + 0, y + 1, str, type, c);
//	render_string(render, x + 2, y + 1, str, type, c);
//	render_string(render, x + 1, y + 2, str, type, c);
//
//	/* 中抜き */
//	c = color >> 0 & 0xf;
//	render_string(render, x + 1, y + 1, str, type, c);
//
//	return wh + 0x00020002; /* 縦横2ピクセルづつ増えます */
//↓2009/07/15:コードサイズ低減のため、render_font_framed_param[]を使うように変更
	int wh;
	int i;

	for(i = 0; i < ARRAY_SIZE(render_font_framed_param); i++) {
		wh = render_string(render,
			x + render_font_framed_param[i].x,
			y + render_font_framed_param[i].y,
			str,
			type,
			(color >> render_font_framed_param[i].c) & 15);
		if(!wh) {
			return 0;
		}
	}

	return wh + 0x00020002; /* 縦横2ピクセルづつ増える */
//}}2009/07/15:コードサイズ低減のため、render_font_framed_param[]を使うように変更
}

/* 文字列展開バッファとして_def_vbuff[]を利用しています。 */
unsigned int
render_printf_framed(RENDER* render, int x, int y, int type, int color, const char* fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vsprintf(_def_vbuff, fmt, ap);
	va_end(ap);

	return render_string_framed(render, x, y, _def_vbuff, type, color);
}

// * Fri Jul 15 21:06:38 JST 2016 Naoyuki Sawa
// - ヌルレンダラーの定数データ「null_render」を定義しました。
//   実際の描画を行わずに、描画関数の戻り値だけを取得したい場合に、空の描画対象として指定するために、汎用的に利用出来ます。
//   現在の所、ヌルレンダラーを指定する意味が有ると思う関数は、文字列描画関数だけです。
//   文字列描画関数は、戻り値として描画した文字列のサイズを返すので、実際に描画せずに、文字列のサイズだけを取得するために、ヌルレンダラーを指定すると便利です。
//   具体的には、下記の関数に対して、ヌルレンダラーを指定する意味が有ると思います。
//   ・render_font(),render_string(),render_printf(),render_font_framed(),render_string_framed(),render_printf_framed()
//   ・render_font7(),render_string7(),render_printf7(),render_font7_framed(),render_string7_framed(),render_printf7_framed()
//   ・render_font8(),render_string8(),render_printf8(),render_font8_framed(),render_string8_framed(),render_printf8_framed()	下記のrender_object()に関する説明を参照せよ。
//   ・render_fontx(),render_stringx(),render_printfx(),render_fontx_framed(),render_stringx_framed(),render_printfx_framed()	下記のrender_object()に関する説明を参照せよ。
// - RENDER構造体のフィールドを変更する関数には、null_renderを引き渡さないように注意して下さい。
//   例えば、render_save(),render_restore()等には、null_renderを引き渡さないように注意して下さい。
//   (もとより、render_save(),render_restore()は戻り値が無いので、ヌルレンダラーを指定する事自体が無意味であり、これらの関数にヌルレンダラーを引き渡す事は無いと思いまずが。)
//   ただし特例として、render_object()に対してヌルレンダラーを指定する事は、(厳密には良くないのですが)、動作上は問題ありません。
//   render_object()は、一時的にnull_render.context->textureを書き換えますが、元に戻すからです。
//   例えば、render_printf8()等にnull_renderを指定した場合、間接的にrender_object()が呼び出され、render_object()に対してnull_renderが引き渡されますが、正常に動作します。
// - ヌルレンダラーの使用例は、以下の通りです。
//   <例>
//   │//文字列の中心が目標位置になるように、文字列を描画するサンプル
//   │void test() {
//   │  int xy, x, y;
//   │  //目標位置を示すためのクロスハッチを描画する。
//   │  x = DISP_X / 2;
//   │  y = DISP_Y / 2;
//   │  render_line(&render, 0, y, DISP_X, y, 1);
//   │  render_line(&render, x, 0, x, DISP_Y, 1);
//   │  //文字列の描画サイズを取得するために、ヌルレンダラーへのダミー描画を行う。
//   │  xy = render_printf(&null_render, 0, 0, 0, 0, "日本語\n数値 %'d\nAB12", (now * now));
//   │  //                               ~~~~     ~ null_renderへ描画する時の、x,y,colorは何でも構いません。
//   │  //文字列の描画サイズから、文字列の中心を目標位置として描画するための、描画原点座標を求める。
//   │  //ここの計算式を変えれば、文字列の右下を目標位置としたり、文字列の左下を目標位置とする事も可能です。
//   │  x -= LOHALF(xy) / 2;
//   │  y -= HIHALF(xy) / 2;
//   │  //文字列を描画する。
//   │  render_printf(&render, x, y, 0, 3, "日本語\n数値 %'d\nAB12", (now * now));
//   │}
//   或いは、ヌルレンダラーを使わなくても、クリッピング領域を狭めたり、サーフェイス外を指定する事で、実際に描画せずに、文字列のサイズだけを取得する事は可能ではあるのですが、
//   ヌルレンダラーを指定する方が、意図が明確に判り、見た目に判り易いソースコードになると思います。
// - ヌルレンダラーの定義においては、極力、メモリを節約するように工夫しました。
//   もし工夫せずに、RENDER構造体とSURFACE構造体を定義すると、(24+52*8)+12=452バイトも消費してしまいます。
//   今回は、以下の工夫を行って、28バイトしか消費しないようにしました。
//   工夫1. RENDER.context_stack[]は1要素分しか使用しないので、定数データに含めませんでした。
//   工夫2. 1要素分のコンテキストは、RENDER.context_stack[0]を指すのではなく、下記の工夫3で重複させた領域を指すようにしました。
//   工夫3. RENDER構造体と、SURFACE構造体の、値が共通な部分を重複させて定義しました。
//   工夫4. 1要素分のコンテキストスタックの行列部分の値は不定値で構わないので、別のコード領域に重複させて、textureフィールドだけが確実にNULLの値を指すようにしました。
//   詳細は、clippce.c内のnull_renderを定義しているアセンブラブロックを参照して下さい。
extern RENDER null_render;
asm("
		.data
		.align		2
		.global		null_render
		;//																	;//+ 0,4 null_context.matrix.a00 = 不問
		;//																	;//+ 4,4 null_context.matrix.a01 = 不問
		;//																	;//+ 8,4 null_context.matrix.a02 = 不問
		;//																	;//+12,4 null_context.matrix.a03 = 不問
		;//																	;//+16,4 null_context.matrix.a10 = 不問
		;//																	;//+20,4 null_context.matrix.a11 = 不問
null_render:
		.word		null_render+4		;//+ 0,4 null_render.surface      = null_surface						;//+24,4 null_context.matrix.a12 = 不問
		.word		0			;//+ 4,4 null_render.left,top     = 0,0			;//+ 0,4 null_surface.w,h   = 0,0	;//+28,4 null_context.matrix.a13 = 不問
		.word		0			;//+ 8,4 null_render.right,bottom = 0,0			;//+ 4,4 null_surface.vbuff = 不問	;//+32,4 null_context.matrix.a20 = 不問
		.word		(1<<10)			;//+12,4 null_render.front        = 1.0			;//+ 8,4 null_surface.zbuff = 不問	;//+36,4 null_context.matrix.a21 = 不問
		.word		(2<<10)			;//+16,4 null_render.back         = 2.0								;//+40,4 null_context.matrix.a22 = 不問
		.word		null_render-24		;//+20,4 null_render.context      = null_context						;//+44,4 null_context.matrix.a23 = 不問
		.word		0															;//+48,4 null_context.texture    = NULL		render_object()にnull_renderを引き渡した場合、一時的に書き換えられる可能性が有るが、render_object()の終了時にNULLに戻る。
		.code					;//必要(?) asmブロックの後でCコンパイラが自動的に.codeに戻してくれるとは限らない(?)みたいなので、明示的に.codeに戻しておく方が安全です。
");

/*****************************************************************************
 *	ユーティリティ：サウンド
 *****************************************************************************/

#if 0 /*{{2006/07/18変更*/
//static PCEWAVEINFO wave_info[4]; /* 各チャネルのWAVE情報。共有不可！ */
//
//void
//sound_play(int ch, const unsigned char* data)
//{
//	PCEWAVEINFO* wi;
//
//	if(ch < 0 || 3 < ch) DIE(); /* チャネル範囲チェック */
//	if(memcmp(data, "MCPP", 4) != 0) DIE(); /* シグネチャチェック */
//
//	/* このチャネル用のPCEWAVEINFO構造体を書き換える前に、
//	 * まずこのチャネルを確実に停止しておかなければいけません。
//	 */
//	pceWaveAbort(ch);
//
//	/* サウンドデータを再生開始します。 */
//	wi = &wave_info[ch];
//	memcpy(wi, data + 8/*シグネチャとサイズの分*/, sizeof(PCEWAVEINFO));
//	wi->pData = data + 8/*シグネチャとサイズの分*/ + sizeof(PCEWAVEINFO);
//	pceWaveDataOut(ch, wi);
//}
#else /*}}2006/07/18変更{{*/
/* * Tue Jul 18 06:21:30 JST 2006 Naoyuki Sawa
 * - sound_play()にループ回数指定を追加しました。
 * - 以下、sound_play()とsound_endproc()の実装に関するコメントです。
 * - SOUNDINFO.wi.typeに、PW_TYPE_CONTを指定してはいけません。
 *   アプリケーションから渡されるサウンドデータは、64サンプルの倍数とは限らないからです。
 *   P/ECEカーネルのバグにより、64サンプルの倍数でないバッファをストリーム再生することはできません。
 *   このバグは、初版からv1.20現在までのすべてのカーネルに存在します。
 *   また、同じ理由により、SOUNDINFO.wi.nextを環状にセットアップしてループ再生することもできません。
 * - PW_TYPE_CONTを指定しなくても、本ルーチンの使い方においては、問題ありません。
 *   特定チャネルのキューに、同時に二つ以上のバッファをキューイングするタイミングが無いからです。
 *
 *   <例>
 *	Ch0のキュー: (empty)
 *		↓sound_play(0, data, 2);
 *	Ch0のキュー: sound_info[0].wi→キューの先頭なので、カーネル内のmywaveinfo[]にコピーされる
 *		↓再生完了
 *	Ch0のキュー: (empty)
 *		↓sound_endproc(pwi);…残りループ回数=1
 *	Ch0のキュー: sound_info[0].wi→キューの先頭なので、カーネル内のmywaveinfo[]にコピーされる
 *		↓再生完了
 *	Ch0のキュー: (empty)
 *		↓sound_endproc(pwi)…残りループ回数=0
 *	Ch0のキュー: (empty)
 *
 * - PW_TYPE_CONTや環状ループを使わなければ、64サンプルの倍数でなくても大丈夫です。
 *   ただし、P/ECEのサウンドDMA転送は64サンプル/16KHz単位で行われるので、64サンプルの倍数でなければ、
 *   ループのつなぎ目に64/16000[秒]未満の途切れが生じる可能性があります。
 *   64/16000[秒]=0.004[秒]なので、実際に問題になることは無いと思います。
 */
typedef struct _SOUNDINFO {
	int ch;			/* チャネル番号(0～3) */
	PCEWAVEINFO wi;		/* WAVE情報。チャネル発声中は変更しないこと!! */
	int loop;		/* 残りループ回数(>0)、または、無限ループ(<0) */
} SOUNDINFO;
static SOUNDINFO sound_info[4];

static void
sound_endproc(PCEWAVEINFO* pwi)
{
	SOUNDINFO* sc = CONTAINING_RECORD(pwi, SOUNDINFO, wi);

	/* ループ回数が負値ならば、ループ回数を変更せずに、無限ループします。
	 * ループ回数が正値ならば、ループ回数を減らし、残っていたらループします。
	 * - ループ回数=0でここへ来ることは有り得ません。(sound_play()で除外済み)
	 */
	if((sc->loop < 0) || (--sc->loop)) {
		pceWaveDataOut(sc->ch, pwi);
	}
}

//void
//sound_play(int ch, const unsigned char* data, int loop)
//↓2006/12/20変更
void
sound_play(int ch, const void* data, int loop)
{
	SOUNDINFO* si;

	/* まず、確実に停止します。
	 * - sound_stop()の中で、チャネル範囲チェックも行われます。
	 */
	sound_stop(ch);

	/* ループ回数=0ならば、再生しません。
	 * - 「sound_play(ch,data,loop)」は「sound_stop(ch)」と等価です。
	 */
	if(!loop) {
		return;
	}

	/* サウンドデータのシグネチャを確認します。
	 * - ループ回数=0ならば、data=NULLを許容する仕様です。
	 *   そのため、ループ回数=0の判定より後でシグネチャを確認します。
	 */
	if(memcmp(data, "MCPP", 4) != 0) {
		DIE(); /* シグネチャが不正 */
	}

	/* チャネル情報をセットアップします。
	 * - si->chとsi->wi.pfEndProcは固定値なので、二回目以降は格納不要なのですが、
	 *   初回か二回目以降かの判定を行うより、無条件で格納してしまう方が簡単です。
	 * - sound_info[4]の定義時に、si->chとsi->wi.pfEndProcの初期値を指定しておく方法も考えられますが、
	 *   SOUNDINFOは構造体定義がネストしているので、P/ECEのGCCでは危険です。(clipusb.hのコメント参照)
	 *   安全のために、上述の通り、固定値フィールドも毎回格納することにしました。
	 */
	si = &sound_info[ch];
	si->ch = ch;
	memcpy(&si->wi, (const unsigned char*)data + 8/*シグネチャとサイズの分*/, sizeof(PCEWAVEINFO));
	si->wi.pData = (const unsigned char*)data + 8/*シグネチャとサイズの分*/ + sizeof(PCEWAVEINFO);
	si->wi.pfEndProc = sound_endproc;
	si->loop = loop;

	/* 再生を開始します。 */
	pceWaveDataOut(ch, &si->wi);
}
#endif/*}}2006/07/18変更*/

void
sound_stop(int ch)
{
	if(ch < 0 || 3 < ch) DIE(); /* チャネル範囲チェック */

	/* このチャネルのサウンドを停止します。 */
	pceWaveAbort(ch);
}

int
sound_stat(int ch)
{
	if(ch < 0 || 3 < ch) DIE(); /* チャネル範囲チェック */

	/* このサウンドチャネルが、再生中か停止中かを調べます。 */
	return pceWaveCheckBuffs(ch);
}

//{{2013/07/15追加
void
sound_att(int ch, int att)
{
	if(ch < 0 || 3 < ch) DIE(); /* チャネル範囲チェック */

	/* このサウンドチャネルの、減衰量を設定します。 */
	pceWaveSetChAtt(ch, att);
}
//}}2013/07/15追加

/****************************************************************************
 *	ユーティリティ：ストリーム再生
 ****************************************************************************/

#define CH_STREAM	0		/* 再生チャネル：0～3に変更可               */
#define BLKN		2		/* ブロック数：調整可、2以上。たぶん2で充分 */

typedef struct _STREAMINFO {
	/*{{オフセット固定部分：asmブロックからオフセット決め打ちで参照します。             */
	PCEKSENT old_isr;		/* + 0,4:元のサウンド割り込みルーチン               */
	//volatile char busy;		/* + 4,1:stream_fill()再入禁止フラグ (bit0のみ使用) */
	//volatile char bc;		/* + 5,1:空きブロック数                             */
	//         char bi;		/* + 6,1:空きブロックの先頭インデクス               */
	//         char reserved;	/* + 7,1:(未使用)                                   */
	//↓* Sun Jan 09 04:51:00 JST 2005 Naoyuki Sawa
	//↓- stream_fill()でのbc--にpceSyncSafetyAddition()を利用できるよう、フィールドの型をchar→intへ変更しました。
	//↓- busy、biの扱いには変更ないのでcharのままでも良かったのですが、今後のためにintに揃えておくことにしました。
	//volatile int busy;		/* + 4,4:stream_fill()再入禁止フラグ (bit0のみ使用) */
	//volatile int bc;		/* + 8,4:空きブロック数                             */
	//int bi;			/* +12,4:空きブロックの先頭インデクス               */
	//↓* Mon Dec 22 21:06:44 JST 2014 Naoyuki Sawa
	//↓- 「ヒープマネージャAPIが排他制御されない問題」(Fri Dec 19 22:30:18 JST 2014)を回避するために、STREAMINFO.busyを廃止し、擬似的な割り込みレベル1を用いてstream_fill()を実行するように変更しました。
	volatile int bc;		/* + 4,4:空きブロック数                             */
	         int bi;		/* + 8,4:空きブロックの先頭インデクス               */
	/*}}オフセット固定部分：asmブロックからオフセット決め打ちで参照します。             */
	int BLKS;			/* コールバック一回当りの展開サンプル数             */
	PCEWAVEINFO wi[BLKN];		/* WAVEヘッダ(バッファは実行時に確保)               */
	STREAMCALLBACK* callback;	/* サンプル展開コールバック                         */
	int param;			/* コールバックへの任意パラメータ                   */
    //{{2014/09/20変更:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
    //	/* * コールバックを呼び出す前に、スタックをFRAM4(高速RAM)領域に切り替えることができます。
    //	 *   通常のスタックをSRAM領域にとっている場合は、この方法で数割程度の高速化が見込めます。
    //	 * * FRAM4領域は、カーネルAPIが同期的に利用する共有領域です。
    //	 *   例えばpceLCDTrans()は、FRAM4領域にピクセル形式変換プログラムを転送し、これを利用し、そして処理を返します。
    //	 *   pceFileWriteSct()は、FRAM4領域にフラッシュ書き換えプログラムを転送し、これを利用し、そして処理を返します。
    //	 *   このように、FRAM4領域は前面スレッドだけで利用され、カーネルが割り込み処理内で利用することはありません。
    //	 * * この性質を利用して、ユーザー割り込み処理内でFRAM4領域を使うことができます。
    //	 *   ユーザー割り込み処理の先頭でFRAM4領域を全て退避し、FRAM4領域を使用し、処理を返す前にFRAM4領域を復元します。
    //	 *   上述の手順を踏めば、前面スレッドのカーネルAPIからは、割り込み処理内でFRAM4領域が使われたことはわかりません。
    //	 * * 割り込み処理内でFRAM4領域をスタックとして利用し、処理の高速化を図るためには、いくつかの条件があります。
    //	 *   1. 高速RAMスタック使用による速度向上が、FRAM4領域退避・復元の時間を上回っていること。
    //	 *      FRAM4領域のサイズは512バイト(0x0e00～0x1000)なので、512バイト×2のコピー時間がオーバーヘッドとなります。
    //	 *      通常は、この程度のオーバーヘッドよりも、高速RAMスタック使用による速度向上の方が大きいはずです。
    //	 *   2. ユーザー定義のコールバック関数が、スタック領域を消費しすぎないこと。
    //	 *      ユーザー定義のコールバック関数が512バイト以上スタックを消費すると、FRAM4領域からあふれてしまいます。
    //	 *      ★★★この問題は簡単に検出できないので、デバッグが大変です！！くれぐれも注意してください！！★★★
    //	 *      実際には、大きな配列や構造体変数を関数ローカルに置いたりしない限り、100バイト前後しか消費しないことがほとんどだと思います。
    //	 *   3. 通常のスタックを高速RAM(0x1000～0x2000)に取っている場合は、スタックをFRAM4領域に切り替えるメリットがありません。
    //	 *      512バイト×2のコピー時間と、FRAM4領域退避用に確保したメモリが無駄になるだけです。
    //	 * * スタック切り替えを行う場合は、fram4_saveにFRAM4領域退避用メモリを確保します。
    //	 *   即ち fram4_save!=NULL ならば、スタック切り替えを行うことを意味します。
    //	 *   fram4_save==NULLならば、スタック切り替えを行わないことを意味します。
    //	 */
    //	void* fram4_save;
    //↓2014/09/20変更:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
	unsigned char fram4;		/* FRAM4領域へのスタック切り替えを行うかのフラグ */
    //}}2014/09/20変更:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
} STREAMINFO;
static STREAMINFO stream_info;

//{{2014/09/22追加:ストリームフィルタを追加しました。
/* * Mon Sep 22 00:22:24 JST 2014 Naoyuki Sawa
 * - ストリームフィルタ関数へのポインタです。NULLならば、ストリームフィルタ関数が設定されていません。
 * - この変数を、stream_infoのメンバでなく、独立のstatic変数とした理由は、以下のとおりです。
 *   ストリームフィルタ関数は、stream_play()～stream_stop()を超えて、維持する仕様としています。
 *   具体的には、stream_play()を呼び出す前にstream_filter_install()を呼び出しても、設定されたストリーム関数は有効のままですし、
 *   stream_filter_install()を呼び出した後に、stream_play(),stream_stop()を行っても、設定されたストリーム関数は有効のままです。
 *   そのためには、stream_filterがstream_play()でクリアされてはいけないので、stream_infoのメンバでなく独立のstatic変数としました。
 * - ストリームフィルタ関数は、stream_play()～stream_stop()を超えて、維持する仕様とした理由は、以下のとおりです。
 *   ストリームフィルタ関数は、stream_play()を呼び出すサウンドモジュール(clipadp.cやcliptsp.cなど)とは、別の概念であります。
 *   サウンドモジュールがミキシングした結果を、変更したり、別の処理に利用したりするための仕組みです。
 *   そのためには、stream_play()やstream_stop()が、ストリームフィルタ関数の設定には影響しない方が、扱い易いです。
 *   従って、ストリームフィルタ関数は、stream_play()～stream_stop()を超えて、維持する仕様としました。
 * - <使用例>
 *   │Audio_Start(0);//USB Audio Deviceを開始する｡
 *   │stream_filter_install((STREAMFILTER*)Audio_Write);//ｽﾄﾘｰﾑﾌｨﾙﾀを設定する｡
 *   │TrueSpeech_Init(SOUND_MAXCH);//ｻｳﾝﾄﾞﾓｼﾞｭｰﾙを開始する｡
 *   │TrueSpeech_PlayCh(0,pData,1);//ｽﾄﾘｰﾑ再生を開始する｡これで､ｻｳﾝﾄﾞﾃﾞｰﾀが､ｽﾄﾘｰﾑﾌｨﾙﾀを経由し､USB Audio Deviceへ送り込まれる｡ 
 */
static STREAMFILTER* stream_filter;
//}}2014/09/22追加:ストリームフィルタを追加しました。

//static void stream_isr();
//↓2004/12/12 C言語関数内のasmブロックでなく、グローバルasmブロックで定義するよう変更したので、staticが使えなくなりました。
void stream_isr();
static void stream_fill();
//static void stream_fill_busy() __attribute__((unused)); /* asmブロックから使うのでCコンパイラが使用を検出できない */
//→2004/12/12 stream_isrの実装変更に伴い、不要になりました。
static void stream_endproc(PCEWAVEINFO* wi);
///*static*/ int internal_stream_callback(short* wbuff, int param, int stack_addr, STREAMCALLBACK* callback); /* 仕方なくglobal... */
//→2005/01/05 汎用化してstack_switch()としました。

//{{2014/12/22追加:「ヒープマネージャAPIが排他制御されない問題」(Fri Dec 19 22:30:18 JST 2014)を回避するために、STREAMINFO.busyを廃止し、擬似的な割り込みレベル1を用いてstream_fill()を実行するように変更しました。
/*static*/ void hdm0_isr();
//}}2014/12/22追加:「ヒープマネージャAPIが排他制御されない問題」(Fri Dec 19 22:30:18 JST 2014)を回避するために、STREAMINFO.busyを廃止し、擬似的な割り込みレベル1を用いてstream_fill()を実行するように変更しました。

/* * Sat Mar 26 07:24:00 JST 2005 Naoyuki Sawa
 * - pceWave*()でストリーム再生を行うときは、バッファを64の倍数にしないと落ちます。
 *   その原因は、カーネルのサウンド再生ルーチンにBugがあるからです。
 * - 問題の個所は、/usr/PIECE/sysdev/pcekn/snd.cの349行～の部分です。(PIECE KERNEL : Ver 1.20)
 *
 *		if ( !pwi->len ) {					…①
 *			wp->pwiee = wp->pwi00;				…②
 *			//wp->pwiee->stat = PW_STAT_END; 要らない
 *			setpwi( wp, wp->pwi, pwi = pwi->next );		…③ ここが間違い!!
 *			if ( !pwi ) break;				…④
 *		}
 *
 *   サウンド再生ルーチンは、アプリケーション指定のバッファから64サンプルづつ取り出し、PWM出力へのDMA転送データを組み立てます。
 *   バッファの残りがなくなると(①)、アプリケーションへの完了通知のために空になったバッファを記憶した上で(②)、次のバッファを繰り上げます(③)。
 *   もし、次のバッファが無ければ、ループを抜けます(④)。
 * - 問題なのは、③の部分です。
 *   サウンド再生ルーチンは、現在再生中のバッファについて、アプリケーションから指定されたPCEWAVEINFOをそのまま参照するのではなく、
 *   現在再生中のバッファについてだけは、コピーを取って使用します。(setpwi()関数、285行目)
 *   なぜかというと、バッファの残りサンプル数を減らすために、PCEWAVEINFO.lenを書き換えているかです。
 *   64サンプルの変換を行うループの最初、326行目のところで、
 *
 *		PCEWAVEINFO *pwi = wp->pwi;
 *
 *   という処理がありますが、これはオリジナルのPCEWAVEINFOではなく、setpwi()によってコピーしたPCEWAVEINFOを指します。
 *   ところが、先ほどの③の部分で、次のバッファを繰り上げたとき、pwiはコピー先のPCEWAVEINFOではなく、オリジナルのPCEWAVEINFOを指してしまっています。
 *
 *		setpwi( wp, wp->pwi, pwi = pwi->next );		…③
 *		            ~~~~~~~        ~~~~~~~~~
 *		コピー先のPCEWAVEINFO領域  オリジナルのPCEWAVEINFOポインタ
 *
 *   バッファサイズが64の倍数ならば、pwiがオリジナルのPCEWAVEINFOを指した直後にループを抜けるので、このポインタが使われることは無く、
 *   (偶然にも)エラーは回避されます。
 *   ところが、バッファサイズが64の倍数でなければ、オリジナルのPCEWAVEINFOを指しているpwiが使われて、オリジナルのPCEWAVEINFOが書き換えられます。
 *   次回の割り込みでサウンド再生ルーチンが呼ばれたときは、326行目の
 *
 *		PCEWAVEINFO *pwi = wp->pwi;
 *
 *   から始まるので、オリジナルのPCEWAVEINFOではなく、コピー先のPCEWAVEINFOが書き換えられます。
 *   つまり、不整合が生じます。(そもそも、オリジナルのPCEWAVEINFOが書き換えられるだけでも問題有りです。)
 * - ③の部分は、正しくは次のように書くべきです。
 *
 *		setpwi( wp, wp->pwi, pwi = pwi->next );		…間違い(現状)
 *		↓
 *		setpwi( wp, pwi = wp->pwi, pwi->next );		…正解
 *
 *   実際にこのように変更してみて、正しくストリーム再生が動作することを確認しました。
 *   もし、もういちどカーネル更新の機会があれば、この修正もお願いしたいところです。
 */

void
//{{2014/12/25変更:stream_play()の第3引数をfram4⇒flagsに変更しました。
//stream_play(int BLKS, STREAMCALLBACK* callback, int param, int fram4)
//↓2014/12/25変更:stream_play()の第3引数をfram4⇒flagsに変更しました。
stream_play(int BLKS, STREAMCALLBACK* callback, int param, int flags)
//}}2014/12/25変更:stream_play()の第3引数をfram4⇒flagsに変更しました。
{
	STREAMINFO* si = &stream_info;

	int i;
	PCEWAVEINFO* wi;

	/* まず、確実に停止します。 */
	stream_stop();

	/* 全体をクリアします。 */
	memset(si, 0, sizeof(STREAMINFO));

	/* WAVEバッファサイズ、コールバック、パラメータを格納します。 */
	if(BLKS % 64 != 0) DIE(); /* pceWave*()でストリーム再生を行うときは、バッファを64の倍数にしないと落ちます →(2005/03/26追記: その理由について、この関数の前にコメントしました) */
	si->BLKS = BLKS;
	si->callback = callback;
	si->param = param;

    //{{2014/09/20変更:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
    //	/* スタック切り替えを行うならば、FRAM4領域退避用メモリを確保します。 */
    //	if(fram4) {
    //		si->fram4_save = malloc(FRAM4_SIZE);
    //		if(si->fram4_save == NULL) DIE();
    //	}
    //↓2014/09/20変更:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
	/* FRAM4領域へのスタック切り替えを行うかのフラグを格納します。 */
      //{{2014/12/25変更:stream_play()の第3引数をfram4⇒flagsに変更しました。
      //si->fram4 = fram4;
      //↓2014/12/25変更:stream_play()の第3引数をfram4⇒flagsに変更しました。
	si->fram4 = flags & STREAM_PLAY_STACK_SWITCH_FRAM4;
      //}}2014/12/25変更:stream_play()の第3引数をfram4⇒flagsに変更しました。
    //}}2014/09/20変更:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。

	/* WAVEヘッダを初期化します。同時にWAVEバッファも確保します。 */
	for(i = 0, wi = si->wi; i < BLKN; i++, wi++) {
		wi->type = PW_TYPE_16BITPCM; /* 輪にするのでPW_TYPE_CONT不要 */
		wi->pData = malloc(sizeof(short) * BLKS); if(wi->pData == NULL) DIE();
		wi->len = BLKS;
		wi->next = &si->wi[(i + 1) % BLKN]; /* 輪にします */
		wi->pfEndProc = stream_endproc;
	}

//{{2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
//DISABLE;
//↓2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
ENTER_CS;
//}}2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
	/* 再生開始前に、全ブロックにデータを満たしておきます。 */
	si->bc = BLKN;
	stream_fill();
    //{{2014/12/22追加:「ヒープマネージャAPIが排他制御されない問題」(Fri Dec 19 22:30:18 JST 2014)を回避するために、STREAMINFO.busyを廃止し、擬似的な割り込みレベル1を用いてstream_fill()を実行するように変更しました。
	/* 割り込みコントローラの「高速DMA Ch.0」のエントリを利用して擬似的な割り込みレベル1を起動することにした。以下はそのための準備です。 */
	bINT_FDMA_FHDM0 = 1;			//高速DMA Ch.0割り込み要因フラグを確実に1にする。実際には不要。電源On後BIOSが一度でもLCD転送を行うと1になり、電源Offするまで0に戻らないので、既に1であるはずだから。	厳密にはここでもRead-modify-writeの問題で高速DMA Ch.1のフラグが欠落する危険があるが、おそらくサウンド出力前(=高速DMA Ch.1起動前)であり大丈夫だろう。もし問題が起きた場合は、この行を削除すれば良い。
      //{{2014/12/25変更:stream_play()の第3引数をfram4⇒flagsに変更しました。
      //bINT_PHSD01L_PHSD0L = 1;		//高速DMA Ch.0割り込みプライオリティを1にする。/usr/PIECE/sysdev/pcekn/lcd.cのInitSPI()が6に設定しているが、古い設計の名残であり使用されておらず、変更して構わない。
      ////└→アプリケーション定義のストリームコールバック処理の中で、ヒープAPIやGCを使用*しない*ことが確実である場合は、アプリケーションにて『bINT_PHSD01L_PHSD0L = 2;』を実行することによって、音飛びを低減できます。	詳細はclippce.hの「割り込みレベル設定」の、'Mon Dec 22 21:06:44 JST 2014'のコメントを参照してください。
      //↓2014/12/25変更:stream_play()の第3引数をfram4⇒flagsに変更しました。
	bINT_PHSD01L_PHSD0L = (flags & STREAM_PLAY_CALLBACK_USES_HEAP) ? 1 : 2;		//高速DMA Ch.0割り込みプライオリティを1,又は,2にする。/usr/PIECE/sysdev/pcekn/lcd.cのInitSPI()が6に設定しているが、古い設計の名残であり使用されておらず、変更して構わない。
      //}}2014/12/25変更:stream_play()の第3引数をfram4⇒flagsに変更しました。
	pceVectorSetTrap(TRAP_HDM0, hdm0_isr);	//高速DMA Ch.0割り込みサービスルーチンを登録する。実際の高速DMA Ch.0完了割り込みではなく、割り込みイネーブルフラグを操作して擬似的な割り込みレベル1として使用する。
	//★重要★
	//┃これ以降「bINT_EDMA_EHDM0=1」を行うことで擬似的な割り込みレベル1を起動できる。Read-modify-writeの問題を避けるために、割り込み要因フラグでなく割り込みイネーブルフラグを操作して起動する工夫をした。
	//┃割り込みハンドラでは「bINT_EDMA_EHDM0=0」を行うこと。割り込み要因フラグをクリアしてはいけない。割り込み要因フラグをクリアすると、Read-modify-writeの問題で高速DMA Ch.1のフラグが欠落する危険がある。
    //}}2014/12/22追加:「ヒープマネージャAPIが排他制御されない問題」(Fri Dec 19 22:30:18 JST 2014)を回避するために、STREAMINFO.busyを廃止し、擬似的な割り込みレベル1を用いてstream_fill()を実行するように変更しました。
	/* サウンド割り込みルーチンをフックし、Wave再生を開始します。
	 * この二つ処理の間にサウンド割り込みがかかってはいけません。 */
	si->old_isr = pceVectorSetTrap(TRAP_HDM1, stream_isr);
	pceWaveDataOut(CH_STREAM, &si->wi[0]);
//{{2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
//ENABLE;
//↓2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
LEAVE_CS;
//}}2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
}

void
stream_stop()
{
	STREAMINFO* si = &stream_info;

	int i;
	PCEWAVEINFO* wi;

	/* 再生中でなければ何もしません。 */
	//if(si->old_isr == NULL) return;
	//↓2004/11/27 stream_stat()新設に伴い、stream_stat()を利用するように変更
	if(!stream_stat()) return;

	/* WAVE再生を停止し、サウンド割り込みルーチンを元に戻します。
	 * この二つ処理の間にサウンド割り込みがかかってはいけません。
	 */
//{{2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
//DISABLE;
//↓2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
ENTER_CS;
//}}2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
	pceWaveAbort(CH_STREAM);
	pceVectorSetTrap(TRAP_HDM1, si->old_isr);
	si->old_isr = NULL;
    //{{2014/12/22追加:「ヒープマネージャAPIが排他制御されない問題」(Fri Dec 19 22:30:18 JST 2014)を回避するために、STREAMINFO.busyを廃止し、擬似的な割り込みレベル1を用いてstream_fill()を実行するように変更しました。
	bINT_EDMA_EHDM0 = 0;			//高速DMA Ch.0割り込みイネーブルフラグを確実に0にする。実際にはここでbINT_EDMA_EHDM0=1であることは無いはずだが(もしそうならばENTER_CSの前に割り込みが発生するはず)、念のためクリアすることにした。削除しても良いと思う。
	//bINT_FDMA_FHDM0,bINT_PHSD01L_PHSD0L,pceVectorSetTrap(TRAP_HDM0,～)を復元する必要は無い。詳細はstream_play()内のコメントを参照せよ。
    //}}2014/12/22追加:「ヒープマネージャAPIが排他制御されない問題」(Fri Dec 19 22:30:18 JST 2014)を回避するために、STREAMINFO.busyを廃止し、擬似的な割り込みレベル1を用いてstream_fill()を実行するように変更しました。
//{{2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
//ENABLE;
//↓2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。
LEAVE_CS;
//}}2014/12/19変更:stream_play(),stream_stop()の中のDISABLE/ENABLEをENTER_CS/LEAVE_CSに変更しました。ここでは実質どちらでも問題ありませんが、万一割り込み禁止状態で呼ばれた場合にENTER_CS/LEAVE_CSの方が安全です。なぜDISABLE/ENABLEにしていたのか覚えていないのですが、多分ENTER_CS/LEAVE_CSを作る前に書いた処理だと思います。

	/* WAVEバッファを開放します。 */
	for(i = 0, wi = si->wi; i < BLKN; i++, wi++) {
		free((void*)wi->pData);
	}

    //{{2014/09/20削除:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
    //	/* FRAM4領域退避用メモリを開放します。 */
    //	free(si->fram4_save);	/* 確保していなくても free(NULL) は安全です */
    //}}2014/09/20削除:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
}

int
stream_stat()
{
	/* 再生中ならば1、停止中ならば0を返します。 */
	return stream_info.old_isr != NULL;
}

//{{2014/09/22追加:ストリームフィルタを追加しました。
void
stream_filter_install(STREAMFILTER* filter)
{
	/* ストリームフィルタ関数を格納します。 */
	stream_filter = filter;
}
//}}2014/09/22追加:ストリームフィルタを追加しました。

#if 0	/*{{このブロックは2004/12/12まで安定して動いていたので、念のため残してあります。*/
//static void
//stream_isr()
//{
//	/* [%sp+ 0]:psr
//	 * [%sp+ 1]:pc
//	 */
//	asm("
//	pushn %r15
//	ld.w %r0, %alr
//	ld.w %r1, %ahr
//	pushn %r1
//	");
//	/* [%sp+ 0]:alr
//	 * [%sp+ 1]:ahr
//	 * [%sp+ 2]:r0
//	 * [%sp+ 3]:r1
//	 * [%sp+ 4]:r2
//	 * [%sp+ 5]:r3
//	 * [%sp+ 6]:r4
//	 * [%sp+ 7]:r5
//	 * [%sp+ 8]:r6
//	 * [%sp+ 9]:r7
//	 * [%sp+10]:r8
//	 * [%sp+11]:r9
//	 * [%sp+12]:r10
//	 * [%sp+13]:r11
//	 * [%sp+14]:r12
//	 * [%sp+15]:r13
//	 * [%sp+16]:r14
//	 * [%sp+17]:r15
//	 * [%sp+18]:psr
//	 * [%sp+19]:pc
//	 */
//	asm("
//	 ld.w %r0, [%sp+18]		; psr
//	xld.w %r3, stream_info
//	 ld.w %r4, [%r3]+		; old_isr
//	xld.w %r1, stream_fill_busy
//	xld.w %r2, stream_isr_ret_busy
//	btst [%r3], 0			; busy?
//	jrne stream_isr_busy
//	xld.w %r1, stream_fill
//	xld.w %r2, stream_isr_ret
//	bset [%r3], 0			; busy <- 1
//stream_isr_busy:
//	pushn %r2
//	");
//	/* %r3     :&busy
//	 * %r4     :old_isr
//	 * [%sp+ 0]:psr
//	 * [%sp+ 1]:stream_fill/busy
//	 * [%sp+ 2]:stream_isr_ret/busy
//	 * [%sp+ 3]:alr
//	 * [%sp+ 4]:ahr
//	 * [%sp+ 5]:r0
//	 * [%sp+ 6]:r1
//	 * [%sp+ 7]:r2
//	 * [%sp+ 8]:r3
//	 * [%sp+ 9]:r4
//	 * [%sp+10]:r5
//	 * [%sp+11]:r6
//	 * [%sp+12]:r7
//	 * [%sp+13]:r8
//	 * [%sp+14]:r9
//	 * [%sp+15]:r10
//	 * [%sp+16]:r11
//	 * [%sp+17]:r12
//	 * [%sp+18]:r13
//	 * [%sp+19]:r14
//	 * [%sp+20]:r15
//	 * [%sp+21]:psr
//	 * [%sp+22]:pc
//	 */
//	asm("
//	jp %r4				; * old_isr->(iret)->stream_fill/busy->(ret)->stream_isr_ret/busy
//					;   長時間の割り込み禁止を防ぐため、old_isrがretiするときに
//					;   割り込み発生時のpsr(たいていIE=1)を復元しています。
//					; * 割り込みルーチン内ではフラグを保存する必要があるので、
//					;   stream_isrから帰るときもretではなくretiを使わなければいけません。
//					;   つまり、割り込み発生時のpsrを、二回復元しています。
//stream_isr_ret:
//	bclr [%r3], 0			; S1C33の関数規約により、r3は保持されているはず。
//stream_isr_ret_busy:
//	");
//	/* [%sp+ 0]:alr
//	 * [%sp+ 1]:ahr
//	 * [%sp+ 2]:r0
//	 * [%sp+ 3]:r1
//	 * [%sp+ 4]:r2
//	 * [%sp+ 5]:r3
//	 * [%sp+ 6]:r4
//	 * [%sp+ 7]:r5
//	 * [%sp+ 8]:r6
//	 * [%sp+ 9]:r7
//	 * [%sp+10]:r8
//	 * [%sp+11]:r9
//	 * [%sp+12]:r10
//	 * [%sp+13]:r11
//	 * [%sp+14]:r12
//	 * [%sp+15]:r13
//	 * [%sp+16]:r14
//	 * [%sp+17]:r15
//	 * [%sp+18]:psr
//	 * [%sp+19]:pc
//	 */
//	asm("
//	popn %r1
//	ld.w %ahr, %r1
//	ld.w %alr, %r0
//	popn %r15
//	");
//	/* [%sp+ 0]:psr
//	 * [%sp+ 1]:pc
//	 */
//	asm("
//	reti
//	");
//}
//static void
//stream_fill_busy()
//{
//	/* 何もしません。 */
//}
#elif 0	/*{{このブロックは2004/12/12～2014/12/22まで安定して動いていたので、念のため残してあります。*/
///* * Sun Dec 12 18:10:00 JST 2004 Naoyuki Sawa
// * - stream_isrのスタック消費量を低減しました。変更理由は、以下の説明を読んでください。
// * - ストリーム再生バッファを大きめに確保した場合、何重にも多重割り込みが発生して、スタックが溢れることがありました。(GYM Player MMC対応版作成中に発生)
// *   stream_isrの実装において、これまでは初回割り込み・多重割り込みにかかわらず全レジスタ(%r0～%r15,%alr,%ahr)を退避・復元していましたが、
// *   多重割り込み時のスタック消費量を低減するために、多重割り込み時は最低限のレジスタ(%r0,%r1)だけを退避・復元するように変更しました。
// *   この変更により、ストリーム再生バッファを 320×3 サンプル程度確保してもスタックが溢れないことを確認しました。(GYM Player MMC対応版作成完了にて確認)
// */
//asm("
//	.code
//	.align	1
//	.global stream_isr
//stream_isr:
//	;// [%sp+0]:psr
//	;// [%sp+1]:pc
//	pushn %r1
//	;// [%sp+0]:r0
//	;// [%sp+1]:r1
//	;// [%sp+2]:psr
//	;// [%sp+3]:pc
//	ld.w %r0, [%sp+2]		;// psr
//	xld.w %r1, stream_isr_busy_ret
//	xbtst [stream_info+4], 0	;// stream_info.busy ?
//	xjrne stream_isr_busy
//	xbset [stream_info+4], 0	;// stream_info.busy <- 1
//	xld.w %r1, stream_isr_ret
//stream_isr_busy:
//	sub %sp, 2
//	ld.w [%sp+0], %r0		;// psr ... 長期間の割り込み禁止によるミキシング中断を防ぐため、old_isrからのiret時に割り込み発生直前の%psrを復元します。
//	ld.w [%sp+1], %r1		;// stream_isr_ret or stream_isr_busy_ret
//	;// [%sp+0]:psr
//	;// [%sp+1]:stream_isr_ret or stream_isr_busy_ret
//	;// [%sp+2]:r0
//	;// [%sp+3]:r1
//	;// [%sp+4]:psr
//	;// [%sp+5]:pc
//	xld.w %r1, [stream_info+0]	;// old_isr
//	jp %r1
//stream_isr_ret:				;// busy=0(初回割り込み)ならば、old_isrからの戻り位置はここです。上の処理で既にbusy=1にしてあります。
//	;// ★old_isrからのiret時に割り込み直前の%psrを復元しているので、これ以降は割り込み許可状態です★
//	;// [%sp+0]:r0
//	;// [%sp+1]:r1
//	;// [%sp+2]:psr
//	;// [%sp+3]:pc
//	ld.w %r0, %alr
//	ld.w %r1, %ahr
//	pushn %r15
//	xcall stream_fill
//	popn %r15
//	ld.w %ahr, %r1
//	ld.w %alr, %r0
//	;// [%sp+0]:r0
//	;// [%sp+1]:r1
//	;// [%sp+2]:psr
//	;// [%sp+3]:pc
//	xbclr [stream_info+4], 0	;// stream_info.busy <- 0
//stream_isr_busy_ret:			;// busy=1(多重割り込み)ならば、old_isrからの戻り位置はここです。
//	;// [%sp+0]:r0
//	;// [%sp+1]:r1
//	;// [%sp+2]:psr
//	;// [%sp+3]:pc
//	popn %r1
//	;// [%sp+0]:psr			;// old_isrからのiret時に既に%psrを復元しているので、同じ%psrを二度復元することになりますが、問題ありません。
//	;// [%sp+1]:pc
//	reti
//");
#else	/*{{2014/12/22～現在の最新コードブロックです。*/
//↓* Mon Dec 22 21:06:44 JST 2014 Naoyuki Sawa
//↓- 「ヒープマネージャAPIが排他制御されない問題」(Fri Dec 19 22:30:18 JST 2014)を回避するために、STREAMINFO.busyを廃止し、擬似的な割り込みレベル1を用いてstream_fill()を実行するように変更しました。
asm("
		.code
		.align		1
stream_isr:
;//{{2014/12/24変更:3命令節約しました｡(上:13命令⇒下:10命令)｡上のﾌﾞﾛｯｸはもう不要ですが､｢割り込みｽﾀｯｸ構造([%sp+0]=%psr,[%sp+4]=%pc)を作成するﾃｸﾆｯｸ｣として有益なので､ｺﾒﾝﾄとして残しておくことにします｡
;//		;//[%sp+ 0] := %psr
;//		;//[%sp+ 4] := %pc
;//		pushn		%r0				;//最低限のレジスタ退避
;//		;//[%sp+ 0] := %r0
;//		;//[%sp+ 4] := %psr
;//		;//[%sp+ 8] := %pc
;//		call.d		4				;//[%sp+ 0] := %pc					─┐		←┬割り込みｽﾀｯｸ構造([%sp+0]=%psr,[%sp+4]=%pc)を作成するﾃｸﾆｯｸです｡｢ld.w %r0,%psr;xld.w %r1,retp;pushn %r1｣よりも2命令節約できます｡欠点は､処理ﾌﾛｰが一旦後方へ飛んで戻って来るという､判り辛さが有ります｡
;//		 ld.w		%r0, %psr			;//%r0      := %psr		*delay*			　│		←┤
;//		 ;//[%sp+ 0] := %r0											　│		　│
;//		 ;//[%sp+ 4] := %psr											　│		　│
;//		 ;//[%sp+ 8] := %pc											　│		　│
;//		 popn		%r0				;//最低限のレジスタ復元					←│┐		　│
;//		 ;//[%sp+ 0] := %psr											　││		　│
;//		 ;//[%sp+ 4] := %pc											　││		　│
;//		 reti						;//							　││		　│
;//		;//[%sp+ 0] := %pc											　││		　│
;//		;//[%sp+ 4] := %r0											　││		　│
;//		;//[%sp+ 8] := %psr											　││		　│
;//		;//[%sp+12] := %pc											　││		　│
;//		pushn		%r0				;//[%sp+ 0] := %psr					←┘│		←┘
;//		;//[%sp+ 0] := %psr ┬old_isrがretiで消費する。								　　│
;//		;//[%sp+ 4] := %pc  ┘											　　│
;//		;//[%sp+ 8] := %r0											　　│
;//		;//[%sp+12] := %psr											　　│
;//		;//[%sp+16] := %pc											　　│
;//		xld.w		%r0, [stream_info+0]		;//%r0  := old_isr					　　│
;//		xbset		[0x40271], 0			;//bINT_EDMA_EHDM0 = 1		*anti-interlock*	　　│		擬似的な割り込みレベル1を用いてstream_fill()を実行する。厳密にはこの後(IL=0,IE=1)になった瞬間に起動する。現在の割り込みが多重割り込みだったならば、当関数を抜けた時に即(IL=0,IE=1)になるとは限らない。
;//		jp		%r0				;//goto    old_isr					──┘
;//↓2014/12/24変更:3命令節約しました｡(上:13命令⇒下:10命令)｡上のﾌﾞﾛｯｸはもう不要ですが､｢割り込みｽﾀｯｸ構造([%sp+0]=%psr,[%sp+4]=%pc)を作成するﾃｸﾆｯｸ｣として有益なので､ｺﾒﾝﾄとして残しておくことにします｡
		;//[%sp+ 0] := %psr
		;//[%sp+ 4] := %pc
		pushn		%r1
		;//[%sp+ 0] := %r0
		;//[%sp+ 4] := -
		;//[%sp+ 8] := %psr
		;//[%sp+12] := %pc
		xld.w		%r0, [stream_info+0]		;//%r0     := old_isr
		xbset		[0x40271], 0			;//bINT_EDMA_EHDM0 = 1		*anti-interlock*			擬似的な割り込みレベル1を用いてstream_fill()を実行する。厳密にはこの後(IL=0,IE=1)になった瞬間に起動する。現在の割り込みが多重割り込みだったならば、当関数を抜けた時に即(IL=0,IE=1)になるとは限らない。
		xld.w		[%sp+4], %r0			;//[%sp+4] := old_isr
		;//[%sp+ 0] := %r0
		;//[%sp+ 4] := old_isr
		;//[%sp+ 8] := %psr
		;//[%sp+12] := %pc
		popn		%r0
		;//[%sp+ 0] := old_isr ──── retが消費する。
		;//[%sp+ 4] := %psr ┬old_isrがretiで消費する。
		;//[%sp+ 8] := %pc  ┘
		ret						;//      goto old_isr
;//}}2014/12/24変更:3命令節約しました｡(上:13命令⇒下:10命令)｡上のﾌﾞﾛｯｸはもう不要ですが､｢割り込みｽﾀｯｸ構造([%sp+0]=%psr,[%sp+4]=%pc)を作成するﾃｸﾆｯｸ｣として有益なので､ｺﾒﾝﾄとして残しておくことにします｡
		;//---------------------------------------------;//
hdm0_isr:
		;//[%sp+0] := %psr
		;//[%sp+4] := %pc
		pushn		%r15				;//┬レジスタ退避
		ld.w		%r0, %alr			;//│
		ld.w		%r1, %ahr			;//│
	;//不要	pushn		%r1				;//┘	;//{{2014/12/27削除:割り込みサービスルーチンの無駄なレジスタ保存を削除しました。C関数が呼出し規約に従い%r0～%r3を保存するので(%alr,%ahr)は(%r0,%r1)に入れたままで大丈夫です。}}
		xbclr		[0x40271], 0			;//bINT_EDMA_EHDM0 = 0							stream_play()内のコメント(★重要★)を参照せよ。
		ld.w		%r9, %psr			;//%r9  := %psr		┬ENABLE;					上位プライオリティの多重割り込みを受け付けます。
		or		%r9, 0x10			;//%r9  := %psr|IE	│
		xcall.d		stream_fill			;//stream_fill()	│
		ld.w		%psr, %r9			;//%psr := %psr|IE	┘	*delay*
	;//不要	popn		%r1				;//┐	;//{{2014/12/27削除:割り込みサービスルーチンの無駄なレジスタ保存を削除しました。C関数が呼出し規約に従い%r0～%r3を保存するので(%alr,%ahr)は(%r0,%r1)に入れたままで大丈夫です。}}
		ld.w		%ahr, %r1			;//│
		ld.w		%alr, %r0			;//│
		popn		%r15				;//┴レジスタ復元
		reti
");
#endif

static void
stream_fill()
{
	STREAMINFO* si = &stream_info;

	int retval;
	short* wbuff;

//{{2014/12/22削除:stream_fill()内の割り込みレベル設定処理を削除しました。stream_fill()は、stream_play()内のENTER_CS～LEAVE_CS区間,及び,hdm0_isr()内の(IL=1,IE=1)区間からしか呼ばれなくなったので、割り込みレベルを設定しなくても既に(IL≧1)の要件を満たしているからです。
//  //{{2014/08/16追加:ストリームコールバック処理中にタイマ割り込みが発生しないよう修正しました。
//	/* * Sat Aug 16 15:46:20 JST 2014 Naoyuki Sawa
//	 * - ストリームコールバック処理中にタイマ割り込みが発生しないよう修正しました。
//	 *   サウンドシーケンサをタイマ割り込み駆動で使用した際に、ストリームコールバック処理中にタイマ割り込みが発生し、
//	 *   サウンドシーケンサの処理を経由で、再生や停止のデバイス関数が呼び出され、問題が生じることを回避するためです。
//	 *   (ストリームコールバック処理を、割り込み許可で実行しているので、このような対策が必要です。割り込み許可になる処理の流れは、stream_isr:を参照してください。)
//	 * - 割り込みレベルが、タイマ割り込みプライオリティ(=6)未満ならば、タイマ割り込みレベルに設定し、タイマ割り込みを禁止する。
//	 *   ミキシング割り込みプライオリティ(=7)は、タイマ割り込みプライオリティよりも高いので、ミキシング割り込みは禁止されない。
//	 *   尚、IE=0だった場合は、ILを変更する事は無駄処理となるが、害は無い。
//	 * - PIECE KERNELの中で、割り込みプライオリティは固定だから、直値で書いても良かったのだが、処理の意味を判り易くするために、レジスタから取得することにした。
//	 * * Sat Sep 20 16:19:04 JST 2014 Naoyuki Sawa
//	 * - USB割込みとタイマ割込みの割込み優先度を下げましたが、以下の処理は引き続き必要です。
//	 *   当関数は、stream_play()経由で呼び出された時も、stream_isr経由で呼び出された時も、どちらも割込み許可(IE=0,IL=0)で呼び出されるからです。
//	 *   以下の処理を行わないと、ストリームコールバック処理中にUSB割込みやタイマ割込みが発生してしまいます。
//	 *   ストリームコールバック処理中にUSB割込みやタイマ割込みが発生してはいけない理由は、lower_restore_interrupt_priority()のコメントを参照してください。 */
//    //{{2014/12/19変更:stream_fill()が、割り込みレベル設定の関数を利用するように変更しました。動作内容はこれまでと変わりません。
//    //	const struct psr psr = get_psr();
//    //	int timer_il = bINT_P16T01_P16T0/*=6*/;	//タイマ割り込みプライオリティ(=6)。\usr\PIECE\sysdev\pcekn\timer.c(381行)参照。⇒「bp[0x266] = 6;」	⇒{{2014/09/20追記:timer.c(381行)によるtimer_il=6ではなく、lower_restore_interrupt_priority()によるtimer_il=1が得られるようになりました。lower_restore_interrupt_priority()がUSB割込みとタイマ割込みを同じ優先度に設定する前提で、この行の処理は変更ありません。}}
//    //	if(psr.il < timer_il) {
//    //		struct psr tmp = psr;
//    //		tmp.il = timer_il;
//    //		set_psr(tmp);
//    //	}
//    //↓2014/12/19変更:stream_fill()が、割り込みレベル設定の関数を利用するように変更しました。動作内容はこれまでと変わりません。
//ENTER_IL(bINT_P16T01_P16T0);
//    //}}2014/12/19変更:stream_fill()が、割り込みレベル設定の関数を利用するように変更しました。動作内容はこれまでと変わりません。
//  //}}2014/08/16追加:ストリームコールバック処理中にタイマ割り込みが発生しないよう修正しました。
//}}2014/12/22削除:stream_fill()内の割り込みレベル設定処理を削除しました。stream_fill()は、stream_play()内のENTER_CS～LEAVE_CS区間,及び,hdm0_isr()内の(IL=1,IE=1)区間からしか呼ばれなくなったので、割り込みレベルを設定しなくても既に(IL≧1)の要件を満たしているからです。
	//↑↓この順序は必須です。save_FRAM4()を割込み許可状態で呼び出してはいけないからです。
  //{{2014/09/20追加:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
	if(si->fram4) {
		/* スタック切り替えあり */
		save_FRAM4(1);											/* FRAM4領域退避 */
	}
  //}}2014/09/20追加:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。

	while(si->bc != 0) {
		/* コールバックを呼び出して、サンプルを展開します。 */
		//wbuff = (short*)si->wi[(int)si->bi].pData;
		//↓2004/01/09 stream_info.biをchar→intに型変更したので、もう警告抑制のためのキャストは必要無くなりました。
		wbuff = (short*)si->wi[si->bi].pData;
	  //{{2014/09/20変更:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
	  //	if(si->fram4_save) {
	  //		/* スタック切り替えあり */
	  //		memcpy(si->fram4_save, FRAM4_START, FRAM4_SIZE);					/* FRAM4領域退避 */
	  //↓2014/09/20変更:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
		if(si->fram4) {
			/* スタック切り替えあり */
	  //}}2014/09/20変更:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
			////*(int*)FRAM4_START = STACKCHECK;							/* スタック溢れチェック */
			////↓2004/12/29 スタック検査用の定数を取りやめ、アドレス値で代用するよう変更
			//*(int*)FRAM4_START = (int)FRAM4_START;						/* スタック溢れチェック */
			////retval = internal_stream_callback(wbuff, si->param, FRAM4_END, si->callback);	/* スタック切り替え、コールバック呼び出し */
			////↓2005/01/05 汎用化してstack_switch()としました。
			//retval = stack_switch(FRAM4_END, si->callback, (int)wbuff, si->param);		/* スタック切り替え、コールバック呼び出し */
			////if(*(int*)FRAM4_START != STACKCHECK) DIE();						/* スタック溢れチェック */
			////↓2004/12/29 スタック検査用の定数を取りやめ、アドレス値で代用するよう変更
			//if(*(int*)FRAM4_START != (int)FRAM4_START) DIE();					/* スタック溢れチェック */
			//↓2005/01/07 以上の手順をまとめてstack_switch_FRAM4()としました。
			retval = stack_switch_FRAM4(si->callback, (int)wbuff, si->param);			/* スタック切り替え、コールバック呼び出し */
	  //{{2014/09/20削除:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
	  //		memcpy(FRAM4_START, si->fram4_save, FRAM4_SIZE);					/* FRAM4領域復元 */
	  //}}2014/09/20削除:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
		} else {
			/* スタック切り替えなし */
			retval = si->callback(wbuff, si->param);						/* コールバック呼び出し */
		}
  //{{2014/09/22変更:ストリームフィルタを追加しました。
  //		if(retval != 0) {
  //↓2014/09/22変更:ストリームフィルタを追加しました。
		if(!retval) {
			/* 展開に成功したら、ストリームフィルタ関数があれば、実行します。 */
			if(stream_filter) { (*stream_filter)(wbuff, si->BLKS); }
		} else {
  //}}2014/09/22変更:ストリームフィルタを追加しました。
			/* 展開に失敗したら、無音にします。(演奏終了orデータが壊れてる？) */
			memset(wbuff, 0, sizeof(short) * si->BLKS);
		}

		/* 空きブロックの先頭インデクスを進めます。 */
		if(++si->bi == BLKN) si->bi = 0;

		/* 空きブロック数を減らします。
		 * * stream_fill()はstream_endproc()に割り込まれる可能性があるので、
		 *   「空きブロック数取得→1減らす→格納」の処理の前後で割り込み制御が必要です。
		 * * stream_isr()経由でこの関数が呼ばれた時の、割り込み許可状態は不定なので、
		 *   単純なDISABLE/ENABLEではなく、状態退避・禁止・復元を行う必要があります。
		 * * →よく考えたら、stream_isr()経由で呼ばれた時は、割り込み許可に決まってるような気がしてきました。
		 *   そもそもstream_isr()は高速DMAの完了割り込みハンドラなのですから、
		 *   stream_isr()が呼ばれた＝高速DMAの完了割り込みが処理された＝割り込みが許可されている。
		 * * というわけで、単純なDISABLE/ENABLEでも大丈夫かも知れません。
		 *   まあ、状態退避・禁止・復元した方が確実だし、少なくとも害はないので、このままにしておきます。
		 */
		//asm("
		//pushn %r2
		//ld.w %r0, %psr		; %psr保存
		//xand %r1, %r0, 0xffffffef	; 割り込み禁止
		//ld.w %psr, %r1
		//xld.w %r1, stream_info+5	; stream_info.bc--
		//ld.b %r2, [%r1]
		//sub %r2, 1
		//ld.b [%r1], %r2
		//ld.w %psr, %r0		; %psr復元
		//popn %r2
		//");
		//↓* Sun Jan 09 04:51:00 JST 2005 Naoyuki Sawa
		//↓- stream_fill()でのbc--にpceSyncSafetyAddition()を利用できるよう、フィールドの型をchar→intへ変更しました。
		//↓- 上の旧いコードでは、stream_info.bcはchar型、Offset5として扱っていますが、型変更により現状とは合っていません。
		//↓  今回の変更により、bcをasmブロックからOffset直接指定で操作する必要は無くなりましたが、念のためご注意ください。
		pceSyncSafetyAddition((SYNCVAR*)&si->bc, -1);
	}

  //{{2014/09/20追加:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
	if(si->fram4) {
		/* スタック切り替えあり */
		save_FRAM4(0);											/* FRAM4領域復元 */
	}
  //}}2014/09/20追加:FRAM4領域退避用メモリを1個にし、全体から共用できるようになりました。
	//↑↓この順序は必須です。save_FRAM4()を割込み許可状態で呼び出してはいけないからです。
//{{2014/12/22削除:stream_fill()内の割り込みレベル設定処理を削除しました。stream_fill()は、stream_play()内のENTER_CS～LEAVE_CS区間,及び,hdm0_isr()内の(IL=1,IE=1)区間からしか呼ばれなくなったので、割り込みレベルを設定しなくても既に(IL≧1)の要件を満たしているからです。
//  //{{2014/08/16追加:ストリームコールバック処理中にタイマ割り込みが発生しないよう修正しました。
//	/* * Sat Aug 16 15:46:20 JST 2014 Naoyuki Sawa
//	 * - 割り込みレベルを復元する。
//	 *   割り込みレベルを変更しなかった場合は、無駄処理となるが、害は無い。 */
//    //{{2014/12/19変更:stream_fill()が、割り込みレベル設定の関数を利用するように変更しました。動作内容はこれまでと変わりません。
//    //	set_psr(psr);
//    //↓2014/12/19変更:stream_fill()が、割り込みレベル設定の関数を利用するように変更しました。動作内容はこれまでと変わりません。
//LEAVE_IL;
//    //}}2014/12/19変更:stream_fill()が、割り込みレベル設定の関数を利用するように変更しました。動作内容はこれまでと変わりません。
//  //}}2014/08/16追加:ストリームコールバック処理中にタイマ割り込みが発生しないよう修正しました。
//}}2014/12/22削除:stream_fill()内の割り込みレベル設定処理を削除しました。stream_fill()は、stream_play()内のENTER_CS～LEAVE_CS区間,及び,hdm0_isr()内の(IL=1,IE=1)区間からしか呼ばれなくなったので、割り込みレベルを設定しなくても既に(IL≧1)の要件を満たしているからです。
}

static void
stream_endproc(PCEWAVEINFO* wi)
{
	STREAMINFO* si = &stream_info;

	/* 空きブロック数を増やします。
	 * * PCEWAVEINFO.pfEndProcは割り込み許可状態で呼ばれますが、
	 *   サウンドドライバによって再入禁止されているので、
	 *   「si->bc++」の前後での割り込み制御は必要ありません。
	 * * 再入禁止処理は、sound_isr3()の後半で行われています。
	 *   （snd.c 421～430行参照、BIOS 1.18現在）
	 */
	si->bc++;
}

/****************************************************************************
 *	ユーティリティ：スタック切替コール
 ****************************************************************************/

///* int internal_stream_callback(short* wbuff, int param, int stack_addr, STREAMCALLBACK* callback) */
//asm("
//	.code
//	.align	1
//	.global internal_stream_callback
//internal_stream_callback:
//	pushn %r0
//	ld.w %r0, %sp		; スタック退避
//	ld.w %sp, %r14		; スタック切り替え
//	call %r15		; callback(wbuff,param)
//	ld.w %sp, %r0		; スタック復元
//	popn %r0
//	ret
//");
//↓
//↓ * Wed Jan 5 06:00:00 JST 2005 Naoyuki Sawa
//↓ - internal_stream_callback()を汎用化し、stack_switch()として公開しました。
//↓   internal_stream_callback()とは引数順序が変わったので、注意してください。
//↓
/* int stack_switch(void* sp, void* fn, int arg1, int arg2) */
asm("
	.code
	.align	1
	.global stack_switch
stack_switch:
	pushn %r0
	ld.w %r0, %sp		;// スタック退避
	ld.w %sp, %r12		;// スタック切替
	ld.w %r12, %r14		;//
	call.d %r13		;// result = fn(arg1,arg2)
	ld.w %r13, %r15		;// *delay*
	ld.w %sp, %r0		;// スタック復元
	popn %r0
	ret
");

/* * Fri Jan 7 04:19:00 JST 2005 Naoyuki Sawa
 * - FRAM4領域へのstack_switch()は頻繁に使うので、あらかじめ定義しておくことにしました。
 *   呼び出し前後でスタックが溢れていないことも検査します。
 */
int
stack_switch_FRAM4(void* fn, int arg1, int arg2)
{
	int retval;

	/* スタック先頭に溢れ検査用Wordを格納します。 */
	*(int*)FRAM4_START = (int)FRAM4_START;

	/* スタックをFRAM4領域に切り替えて、関数を呼び出します。 */
	retval = stack_switch(FRAM4_END, fn, arg1, arg2);

	/* スタック先頭の溢れ検査用Wordが破壊されていないことを確認します。 */
	if(*(int*)FRAM4_START != (int)FRAM4_START) {
		DIE();
	}

	/* 関数からの戻り値をそのまま返します。 */
	return retval;
}

/****************************************************************************
 *	ユーティリティ：ファイル
 ****************************************************************************/

//→2002/11/26: OBSOLETEです。fread/fwriteを使ってください。
//int
//file_read(FILEACC* fa, void* ptr, int ofs, int len)
//{
//	int i, sct, sof, sln;
//	char* p = (char*)ptr;
//
//	i = 0;
//	while(i < len) {
//		/* セクタのフラッシュメモリアドレスを取得します。 */
//		sct = (ofs + i) / BLOCKSIZE;			/* sct = 現在の読み込みオフセットを含むセクタ番号                         */
//		sln = pceFileReadSct(fa, NULL, sct, BLOCKSIZE);	/* sln = このセクタのデータサイズ(最終セクタのみsln<=BLOCKSIZEになります) */
//		if(sln == 0) break;				/* たぶんオフセット超過                                                   */
//
//		/* このセクタから読み込むサイズを決定します。 */
//		sof = (ofs + i) % BLOCKSIZE;			/* セクタ内オフセット                                                       */
//		sln -= sof;					/* セクタ内オフセット以降から読み込み可能な最大データサイズ                 */
//		if(sln > len - i) sln = len - i;		/* 読み込み可能な最大データサイズが残り読み込みサイズ以上なら、切り詰めます */
//		if(sln <= 0) break;				/* たぶんオフセット超過、または読み込みサイズに0以下が指定された(len<=0)    */
//
//		/* セクタからバッファへ読み込みます。 */
//		memcpy(p + i, fa->aptr + sof, sln);
//		i += sln;
//	}
//
//	return i;
//}

//→2002/11/26: OBSOLETEです。fread/fwriteを使ってください。
//int
//file_write(FILEACC* fa, const void* ptr, int ofs, int len)
//{
//	int i, sct, sof, sln;
//	char* buf;
//	const char* p = (const char*)ptr;
//
//	/* ※v1.18現在のP/ECEカーネルは、pceFileOpen()に指定されたmodeを無視します。
//	 *   FOMD_RDで開いたファイルに対してもpceFileWriteSct()できますし、
//	 *   FOMD_WRで開いたファイルに対してもpceFileReadSct()できます。
//	 *   file_write()関数に渡されるファイルは、FOMD_WRで開かれているはずですが、
//	 *   上記の特性を利用して、pceFileReadSct()を使ってしまっています。
//	 */
//
//	/* セクタの一部を書き換えるために、セクタ全体をRAMに読み込むバッファを確保します。 */
//	buf = (char*)malloc(BLOCKSIZE);
//	if(buf == NULL) return 0;
//
//	i = 0;
//	while(i < len) {
//		/* セクタのフラッシュメモリアドレスを取得します。 */
//		sct = (ofs + i) / BLOCKSIZE;			/* sct = 現在の書き込みオフセットを含むセクタ番号                             */
//		sln = pceFileReadSct(fa, buf, sct, BLOCKSIZE);	/* このセクタの全データを取得します。(最終セクタのみsln<=BLOCKSIZEになります) */
//		if(sln == 0) break;				/* たぶんオフセット超過                                                       */
//
//		/* このセクタに書き込むサイズを決定します。 */
//		sof = (ofs + i) % BLOCKSIZE;			/* セクタ内オフセット                                                       */
//		sln -= sof;					/* セクタ内オフセット以降に書き込み可能な最大データサイズ                   */
//		if(sln > len - i) sln = len - i;		/* 書き込み可能な最大データサイズが残り書き込みサイズ以上なら、切り詰めます */
//		if(sln <= 0) break;				/* たぶんオフセット超過、または書き込みサイズに0以下が指定された(len<=0)    */
//
//		/* セクタの全データの一部を書き換えます。 */
//		memcpy(buf + sof, p + i, sln);
//		i += sln;
//
//		/* セクタの全データをフラッシュメモリに書き戻します。 */
//		pceFileWriteSct(fa, buf, sct, BLOCKSIZE);	/* 最終セクタの末尾超過分は、pceFileWriteSct()によって切り詰められます  */
//								/* ここで失敗することはありえないはずなので、エラーチェックは行いません */
//	}
//
//	/* バッファを開放します。 */
//	free(buf);
//
//	return i;
//}

//→2002/11/26: OBSOLETEです。unzipを使ってください。
//FILE_INFO*
//fpak_find(FILEACC* fa, const char* fname)
//{
//	int retval, i, ofs;
//	FILE_PAC_INFO fpi;
//	static FILE_INFO fi; /* 戻り値はここを指しています */
//
//	/* ファイルパック情報を読みます。 */
//	retval = file_read(fa, &fpi, 0, sizeof fpi);
//	if(retval != sizeof fpi) return NULL; /* 壊れている */
//	if(fpi.head != FILE_PAC_INFO_HEAD)return NULL; /* シグネチャ不正 */
//	if(fpi.famount < 0) return NULL; /* ファイル数不正 */
//
//	/* ファイル情報を読みます。 */
//	ofs = sizeof fpi;
//	for(i = 0; i < fpi.famount; i++) {
//		retval = file_read(fa, &fi, ofs, sizeof fi);
//		if(retval != sizeof fi) return NULL; /* 壊れている */
//		if(strncmp(fi.fname, fname, sizeof fi.fname) == 0) return &fi; /* 発見 */
//		ofs += sizeof fi; /* 次のファイル情報位置へ */
//	}
//
//	return NULL; /* 見つからなかった */
//}

const void*
file_address(const char* fname, int* size)
{
	int retval, ofs;
	const unsigned char* ptr;
	FILEACC fa;

	/* ファイルを開きます。 */
	retval = pceFileOpen(&fa, fname, FOMD_RD);
	if(retval != 0) return NULL; /* ファイルが存在しない */

	/* セクタが連続しているか調べます。 */
	ptr = NULL; /* 警告抑制 */
	for(ofs = 0; ofs < fa.fsize; ofs += BLOCKSIZE) {
		pceFileReadSct(&fa, NULL, ofs / BLOCKSIZE, BLOCKSIZE);
		if(ofs != 0) {
			if(fa.aptr != ptr + BLOCKSIZE) {
				pceFileClose(&fa);
				return NULL; /* セクタが不連続 */
			}
		}
		ptr = fa.aptr;
	}

	/* ファイルの先頭アドレスを取得します。 */
	pceFileReadSct(&fa, NULL, 0, BLOCKSIZE);
	ptr = fa.aptr;

	/* ファイルサイズが要求されていたら、格納します。 */
	if(size != NULL) *size = fa.fsize;

	/*ファイルを閉じます。 */
	pceFileClose(&fa);

	return ptr;
}

//{{2003/09/14:あまり使わないので、OBSOLATEにしました。
//int
//file_select(const char* header, char* items[], int n_items, const char* footer)
//{
//#define ROWS	((DISP_Y - 10 * 2) / 10)		/* 一画面内に表示できる項目行数 */
//#define Y0	(10 + (DISP_Y - 10 * (2 + ROWS)) / 2)	/* 一行目の表示位置(Y座標) */
//#define X0	( 0 + (DISP_Y - 10 * (2 + ROWS)) / 2)	/* 一桁目の表示位置(X座標)、上と同じ余白を取る */
//
//	unsigned char* vbuff = pceLCDSetBuffer(INVALIDPTR); /* 仮想VRAMアドレスを取得 */
//	int oldsel = -1;  /* 直前の選択項目インデックス(再描画の要/不要判定のため) */
//	int cursel = 0;   /* 現在の選択項目インデックス */
//	int curtop = 0;   /* 画面一番上の表示項目インデックス */
//	int joy, i, x, y;
//
//	if(n_items <= 0) DIE(); /* 少なくとも一つ以上の選択候補が必要 */
//	if(header == NULL) header = "ファイルを選んでください";
//	if(footer == NULL) footer = "[Ａ]：決定／[Ｂ]：取消し";
//
//	pceFontSetType(0x80); /* フォントは5x10、改行なしに固定です */
//	for(;;) {
//		yield();
//		joy = pcePadGet();
//
//		/* 選択確定/取消し？ */
//		if(joy & TRG_A) break;
//		if(joy & TRG_B) { cursel = -1; break; }
//
//		/* 選択項目変更？ */
//		if(joy & TRG_UP) cursel--;
//		if(joy & TRG_DN) cursel++;
//		if(joy & TRG_LF) cursel += ROWS;
//		if(joy & TRG_RI) cursel += ROWS;
//		if(cursel < 0) cursel = 0;
//		if(cursel > n_items - 1) cursel = n_items - 1;
//
//		/* 再描画が必要？ */
//		if(cursel != oldsel) {
//			oldsel = cursel;
//
//			/* スクロール？ */
//			if(cursel < curtop) curtop = cursel;
//			if(cursel > curtop + (ROWS - 1)) curtop = cursel - (ROWS - 1);
//
//			/* 画面クリア。 */
//			memset(vbuff, 0, DISP_X * DISP_Y/*「sizeof vbuff」じゃないよ!*/);
//
//			/* 項目一覧表示。 */
//			for(i = 0; i < n_items; i++) {
//				x = X0;
//				y = Y0 + (i - curtop) * 10;
//				if(i == cursel) {
//					pceFontSetTxColor(0);
//					pceFontSetBkColor(2);
//				} else {
//					pceFontSetTxColor(3);
//					pceFontSetBkColor(-1);
//				}
//				pceFontSetPos(x, y);
//				pceFontPutStr(items[i]);
//			}
//
//			/* ヘッダ・フッタ表示。 */
//			pceFontSetTxColor(0);
//			pceFontSetBkColor(-1);
//			x = (DISP_X - 5 * strlen(header)) / 2;
//			y = 0;
//			memset(vbuff + DISP_X * y, 3, DISP_X * 10);
//			pceFontSetPos(x, y);
//			pceFontPutStr(header);
//			x = (DISP_X - 5 * strlen(footer)) / 2;
//			y = DISP_Y - 10;
//			memset(vbuff + DISP_X * y, 3, DISP_X * 10);
//			pceFontSetPos(x, y);
//			pceFontPutStr(footer);
//
//			/* 画面転送。 */
//			pceLCDTrans();
//		}
//	}
//
//	return cursel;
//
//#undef ROWS
//#undef Y0
//#undef X0
//}
//}}2003/09/14:あまり使わないので、OBSOLATEにしました。
//→2006/07/01追記:もっと汎用的なメニューを、clipmenu.h,clipmenu.cに作成しました。

//{{2005/09/03:mmap()関数をマクロ化しました。→clippce.h
//const void*
//mmap(const char* fname, int* size, int defrag)
//{
//	const void* romp;
//	if(defrag) {
//		romp = file_defrag(fname, size);
//	} else {
//		romp = file_address(fname, size);
//	}
//	return romp;
//}
//}}2005/09/03:mmap()関数をマクロ化しました。→clippce.h

//{{2015/11/18削除:「ユーティリティ：拡張タイマ」を、(clippce.h,clippce.c)⇒(cliptimr.h,cliptimr.c)へ移動しました。
///****************************************************************************
// *	ユーティリティ：拡張タイマ
// ****************************************************************************/
//
////{{2014/12/28変更:拡張タイマテーブルを、固定割り当て⇒動的割り当てに変更しました。
////typedef struct _TIMER {
////	int count;			/* 次回タイムアウトまでの残り時間[ms](0=停止) */
////	int interval;			/* タイムアウト間隔[ms] */
////	void (*callback)(void* context);/* タイムアウト時に呼び出す、コールバック関数 */
////	void* context;			/* コールバック引数 */
////} TIMER;
////static TIMER timers[16/*調整可*/];
/////*--------------------------------------------------------------------------*/
////static void timer_proc() {
////	int id;
////	TIMER* timer;
////	/* 各タイマについて... */
////	for(id = 0; id < ARRAY_SIZE(timers); id++) {
////		timer = &timers[id];
////		/* このタイマが動作中ならば残り時間を減らし、タイムアウトしたら... */
////		if((timer->count)/*動作中?*/ && (!--timer->count)/*タイムアウト?*/) {
////			/* 先に、次回タイムアウトまでの残り時間を初期化しておきます。
////			 * コールバック関数内でタイマを停止した場合、上書きされます。 */
////			timer->count = timer->interval;
////			/* コールバック関数を呼び出します。 */
////			timer->callback(timer->context);
////		}
////	}
////}
/////*--------------------------------------------------------------------------*/
////int timer_start(int interval, void (*callback)(void* context), void* context) {
////	int id;
////	TIMER* timer;
////	if((interval <= 0) || (!callback)) {
////		DIE();	/* パラメータ不正 */
////	}
////ENTER_CS;
////	/* 停止中のタイマを探します。 */
////	for(id = 0; id < ARRAY_SIZE(timers); id++) {
////		timer = &timers[id];
////		if(!timer->count) {
////			break;
////		}
////	}
////	if(id == ARRAY_SIZE(timers)) {
////		DIE();	/* 空きタイマ無し */
////	}
////	/* タイマを初期化します。 */
////	timer->count = timer->interval = interval;
////	               timer->callback = callback;
////	               timer->context  = context ;
////LEAVE_CS;
////	/* 拡張タイマドライブ用の1msコールバックを起動します。
////	 * 実装を簡単にするため、二回目以降も毎回再起動することにします。 */
////	pceTimerSetCallback(0/*変更可*/, PCE_TT_PERIODIC, 1, timer_proc);
////	return id + 1/*タイマIDは1ベース*/;
////}
/////*--------------------------------------------------------------------------*/
////void timer_stop(int id) {
////	TIMER* timer;
////	id--; /*タイマIDは1ベース*/
////	if((id < 0) || (id > ARRAY_SIZE(timers) - 1)) {
////		DIE();	/* タイマID不正 */
////	}
////	timer = &timers[id];
////ENTER_CS;
////	if(!timer->count) {
////		DIE();	/* タイマ停止中 */
////	}
////	/* タイマを停止します。 */
////	timer->count = 0;
////LEAVE_CS;
////}
////↓2014/12/28変更:拡張タイマテーブルを、固定割り当て⇒動的割り当てに変更しました。
//typedef struct _ST_TIMER {
//	int	count;				//+ 0,4 次回タイムアウトまでの残り時間[ms](0=停止)
//	int	interval;			//+ 4,4 タイムアウト間隔[ms]
//	void	(*callback)(void* context);	//+ 8,4 タイムアウト時に呼び出す、コールバック関数
//	void*	context;			//+12,4 コールバック引数
//} ST_TIMER;					//=16
//static int      SIZE_timer;			//拡張タイマテーブルの要素数
//static ST_TIMER* TBL_timer/*[SIZE_timer]*/;	//拡張タイマテープル
///*--------------------------------------------------------------------------*/
////{{2015/09/16変更:timer_proc()が、経過時間に応じた処理回数分まとめて処理するように、変更しました。
////static void timer_proc() {
////	int id;
////	ST_TIMER* timer;
////	//各拡張タイマについて…
////	// - コールバック関数内でtimer_start()が呼ばれた場合、TBL_timerとSIZE_timerが変化する可能性があるので、
////	//   ループ開始前にTBL_timer,SIZE_timerを取得しておくことはできず、ループ毎にグローバル変数を参照する。
////	for(id = 0; id < SIZE_timer; id++) {
////		timer = &TBL_timer[id];
////		//この拡張タイマが動作中ならば残り時間を減らし、タイムアウトしたら…
////		if((timer->count)/*動作中?*/ && (!--timer->count)/*タイムアウト?*/) {
////			//先に、次回タイムアウトまでの残り時間を初期化しておきます。
////			timer->count = timer->interval;
////			//↑この処理順序は必須です。理由は以下の通りです:
////			//│- コールバック関数内でtimer_stop()が呼ばれた場合、timer->countが0に上書きされます。
////			//│  timer_stop()が呼ばれ、さらにtimer_start()が呼ばれて再利用される可能性も有ります。
////			//↓  どちらの場合も、この処理順序で、問題なく処理できます。
////			//コールバック関数を呼び出します。
////			(*timer->callback)(timer->context);
////		}
////	}
////}
////↓2015/09/16変更:timer_proc()が、経過時間に応じた処理回数分まとめて処理するように、変更しました。
//// * Wed Sep 16 01:23:07 JST 2015 Naoyuki Sawa
//// - timer_proc()が、経過時間に応じた処理回数分まとめて処理するように、変更しました。
//// - このように変更したきっかけは、ストリームコールバックや標準エラー出力(USB)を使うと、結構タイマ割り込みが飛ぶ事に気付いたからです。(ストリームコールバックと標準エラー出力(USB)のどちらが主な要因かは未調査です。)
////   例えば、TAPシーケンサはタイマで再生時間のを計測して自然ループや自然停止のコールバックを行うのですが、実際に音の再生がループするよりも自然ループのコールバックがだんだん遅れる事に気付きました。
////   音の再生はミキシング割り込み(DMA割り込み)駆動であり、タイマ割り込みよりも正確であるはずなので、タイマ割り込みの方が遅れているのだと判断しました。
//// - おそらく、ミキシング割り込みが1[ms]以上かかった時に、タイマ割り込みが飛んでいるのだと思いました。
////   そこで、タイマ割り込みが飛んだ後は、経過時間に応じた処理回数分まとめて処理する事にしました。
////   この修正を行った後は、TAPシーケンサの自然ループのコールバックが遅れなくなった事を、動作確認しました。
//// - 本来この修正は、timer_proc()で対応するよりも、タイマ割り込みそのもの(timer_isr())で行う方が望ましいのですが、出来ませんでした。
////   timer_isr()は、スタックフレームの構成を仕様化しているため、経過時間に応じた処理回数分まとめて処理するためのループ処理を入れられないからです。
////   当ソースの後ろの方に有る、new_timer_isrの実装を見れば、その中にループを入れられない事が理解出来ると思います。
//#define TIMER_DT_MAX	10	//経過時間に応じた処理回数の上限(調整可)
//static void timer_proc() {
//	static int t0;	//前回の時刻[ms]。0ならば初回(=未初期化)を意味する。約49日で一周するが実用上問題無い。もし一周して0に一致し再初期化が発生した場合もそれによる誤差は1[ms]程度だろう。
//	int t1, dt, id;
//	if(!t0) { t0 = pceTimerGetCount(); }	//前回の時刻が初回(未初期化)ならば現在の時刻を取得する。
//	          t1 = pceTimerGetCount();	//現在の時刻を取得する。
//	dt = t1 - t0;				//経過時間を求める。
//	     t0 = t1;				//前回の時刻を更新する。
//	if((unsigned)dt > TIMER_DT_MAX) { dt = TIMER_DT_MAX; }	//経過時間が制限を超過したら切り詰める。割り込みが極端に長時間禁止された直後に以下のループが回り過ぎる事を防ぐ安全対策です。
//	while(--dt >= 0) {	//経過時間に応じた処理回数分ループする。経過時間0ならば一度もループしない可能性も有る。
//		//各拡張タイマについて…
//		// - コールバック関数内でtimer_start()が呼ばれた場合、TBL_timerとSIZE_timerが変化する可能性があるので、
//		//   ループ開始前にTBL_timer,SIZE_timerを取得しておくことはできず、ループ毎にグローバル変数を参照する。
//		for(id = 0; id < SIZE_timer; id++) {
//			ST_TIMER* timer = &TBL_timer[id];
//			//この拡張タイマが動作中ならば残り時間を減らし、タイムアウトしたら…
//			if((timer->count)/*動作中?*/ && (!--timer->count)/*タイムアウト?*/) {
//				//先に、次回タイムアウトまでの残り時間を初期化しておきます。
//				timer->count = timer->interval;
//				//↑この処理順序は必須です。理由は以下の通りです:
//				//│- コールバック関数内でtimer_stop()が呼ばれた場合、timer->countが0に上書きされます。
//				//│  timer_stop()が呼ばれ、さらにtimer_start()が呼ばれて再利用される可能性も有ります。
//				//↓  どちらの場合も、この処理順序で、問題なく処理できます。
//				//コールバック関数を呼び出します。
//				(*timer->callback)(timer->context);
//			}
//		}
//	}
//}
////}}2015/09/16変更:timer_proc()が、経過時間に応じた処理回数分まとめて処理するように、変更しました。
///*--------------------------------------------------------------------------*/
//int timer_start(int interval, void (*callback)(void* context), void* context) {
//	int id;
//	ST_TIMER* timer;
//	if((interval <= 0) || (!callback)) { DIE(); }	//引数不正
//ENTER_CS;
//	//拡張タイマテーブルの中から、停止中の拡張タイマを探します。
//	for(id = 0; id < SIZE_timer; id++) {
//		timer = &TBL_timer[id];
//		if(!timer->count) { break; }
//	}
//	//拡張タイマテーブルの中に、停止中の拡張タイマが無かったら…
//	if(id == SIZE_timer) {
//		//拡張タイマテーブルを拡張します。
//		TBL_timer = realloc(TBL_timer, ++SIZE_timer * sizeof(ST_TIMER));
//		if(!TBL_timer) { DIE(); }	//メモリ不足
//	}
//	//拡張タイマを初期化します。
//	timer = &TBL_timer[id];
//	timer->count = timer->interval = interval;
//	               timer->callback = callback;
//	               timer->context  = context ;
//LEAVE_CS;
//	//拡張タイマドライブ用の1msコールバックを起動します。
//	//実装を簡単にするため、二回目以降も毎回再起動することにします。
//	pceTimerSetCallback(0/*変更可*/, PCE_TT_PERIODIC, 1, timer_proc);
//	return id + 1;	//拡張タイマIDは1ベース
//}
///*--------------------------------------------------------------------------*/
//void timer_stop(int id) {
//	ST_TIMER* timer;
//	id--;		//拡張タイマIDは1ベース
//	if((unsigned)id >= SIZE_timer) { DIE(); }	//拡張タイマID不正
//	timer = &TBL_timer[id];
//ENTER_CS;
//	if(!timer->count) { DIE(); }	//既に停止したタイマを二重停止しようとした。
//	//拡張タイマを停止します。
//	timer->count = 0;
//LEAVE_CS;
//}
////}}2014/12/28変更:拡張タイマテーブルを、固定割り当て⇒動的割り当てに変更しました。
//}}2015/11/18削除:「ユーティリティ：拡張タイマ」を、(clippce.h,clippce.c)⇒(cliptimr.h,cliptimr.c)へ移動しました。

/*****************************************************************************
 *	PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチ
 *****************************************************************************/

#if 0 //{{2009/03/31:PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチの方法を変更したことに伴い削除
//#define heapbroken(phm) DIE()
///* heaptopはカーネル内のローカル変数なので直接参照できないため、別の方法で取得します。
// * - P/ECEアプリケーションのメモリレイアウトを利用して、heaptopを取得します。
// *   メモリレイアウトの詳細は、clipgc.cの中の、gc_init()のコメントを参照してください。
// */
//static HEAPMEM* get_heaptop() {
//	extern unsigned int __START_DEFAULT_CODE[];
//	pceAPPHEAD* apphead = (pceAPPHEAD*)__START_DEFAULT_CODE;
//	HEAPMEM* heaptop = (HEAPMEM*)(apphead->bss_end + apphead->stack_size);
//	if(heaptop->mark != HEAPMEMMK) DIE();
//	return heaptop;
//}
////{{PieceSystem1.20の/usr/PIECE/system/pcekn/heapman.cから処理内容の変更無しで引用
//static void SetHEAPMEM(HEAPMEM* phm, int owner, void* chain) {
//	phm->mark = HEAPMEMMK;
//	phm->owner = owner;
//	phm->chain = chain;
//}
//static void CombineFreeHEAPMEM(HEAPMEM* phm) {
//	HEAPMEM* phm0 = phm;
//	phm = phm->chain;
//	while(phm) {
//		if(phm->mark != HEAPMEMMK) heapbroken(phm);
//		if(phm->owner) break;
//		phm = phm->chain;
//		phm0->chain = phm;
//	}
//}
//static void AllocHEAPMEM(HEAPMEM* phm1, unsigned long size, unsigned long size1) {
//	if(size1 > size+sizeof(HEAPMEM)) {
//		HEAPMEM* phm2 = phm1->chain;
//		SetHEAPMEM(phm1, HMO_DEFAULT_USER, (unsigned char*)phm1 + size);
//		SetHEAPMEM(phm1->chain, 0, phm2);
//		CombineFreeHEAPMEM(phm1->chain);
//	} else {
//		phm1->owner = HMO_DEFAULT_USER;
//	}
//}
////}}PieceSystem1.20の/usr/PIECE/system/pcekn/heapman.cから処理内容の変更無しで引用
//static int fix_pceHeapRealloc(void* memp, unsigned long size) {
//	HEAPMEM* phm = get_heaptop()/*heaptop*/; /* heaptopはカーネル内のローカル変数なので直接参照できないため、別の方法で取得します */
//	HEAPMEM* phms = (HEAPMEM*)((unsigned long)memp - sizeof(HEAPMEM));
//	unsigned long oldsize;
//	while(phm) {
//		if(phm->mark != HEAPMEMMK) heapbroken(phm);
//		if(phm->owner && phm == phms) break;
//		phm = phm->chain;
//	}
//	if(!phm) return 1; /* 失敗(そういうメモリはありません) */
//	size = (size + sizeof(HEAPMEM) + 3) & ~3; /* 4の倍数に整列 */
//	if(!size) size = 4;
//	oldsize = (unsigned long)phm->chain - (unsigned long)phm;
//	if(oldsize < size) { /* Heapを増やさないといけない */
//		unsigned long n = size - oldsize;
//		unsigned long n2;
//		HEAPMEM* phm2 = phm->chain;
//		if(phm2->owner) return 2; /* 失敗(後ろに空きがない) */
//		//{{修正パッチ適用箇所はここです!!
//		if(n < sizeof(HEAPMEM)) n = sizeof(HEAPMEM); // ←この一行を追加!!
//		//}}修正パッチ適用箇所はここです!!
//		n2 = (unsigned long)phm2->chain - (unsigned long)phm2;
//		if(n2 < n) return 3;  /* 失敗(後ろに空きが足りない) */
//		AllocHEAPMEM(phm2, n, n2);
//		SetHEAPMEM(phm, HMO_DEFAULT_USER, phm2->chain);
//	} else if(oldsize > size) { /* Heapを減らさないといけない */
//		AllocHEAPMEM(phm, size, oldsize);
//	}
//	return 0; /* 成功 */
//}
//static void bugfix_pceHeapRealloc() {
//	static PCEKSENT bug_pceHeapRealloc = (PCEKSENT)fix_pceHeapRealloc;
//	bug_pceHeapRealloc = pceVectorSetKs(KSNO_HeapRealloc, bug_pceHeapRealloc);
//}
//#undef heapbroken
#endif //}}2009/03/31:PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチの方法を変更したことに伴い削除

/* * Tue Mar 31 15:01:38 JST 2009 Naoyuki Sawa
 * - コードサイズを節約するために、PieceSystem1.20現在のpceHeapRealloc()バグ修正パッチの方法を変更しました。
 * - PieceSystem1.20現在のpceHeapRealloc()のバグの内容は、結局のところ、次のように言い表すことができます。
 * 
 * 	「+1～7バイトのサイズ拡張を行なおうとしたときに、不正な処理となる。」
 * 
 *   下図の、①(サイズ縮小)、②(サイズ変更無し)、④(+8バイトのサイズ拡張)、⑤(+9バイト以上のサイズ拡張)は、
 *   元々のpceHeapRealloc()のままで問題無く処理でき、③(+1～7バイトのサイズ拡張)だけが不正な処理になります。
 * 
 * 	           |               |
 * 	       phm +===============+
 * 	           | mark  | owner |
 * 	           +---------------|
 * 	+--------- |     chain     |
 * 	|     memp +---------------+-----------------------------------
 * 	|        A |               | A ①  A  ②  A  ③  A  ④  A  ⑤  
 * 	|        | |               | |size | size | size | size | size 
 * 	| oldsize| |               | V(ok) | (ok) | (!!) | (ok) | (ok) 
 * 	|        | |               |       |      |      |      |      
 * 	|        V |               |       V      |      |      |      
 * 	+--------> +===============+--------------|------|------|------
 * 	         A | mark  | owner |              V      |      |      
 * 	  8バイト| +---------------|                     |      |      
 * 	         V |     chain     |                     V      |      
 * 	-----------+---------------+----------------------------|------
 * 	           |               |                            V      
 * 
 * - これまでの修正パッチは、pceHeapRealloc()の処理をすべて置き換えていましたが、そこまでする必要は無く、
 *   +1～7バイトのサイズ拡張(③)をフックし、+8バイトのサイズ拡張(④)に置き換えれば良いのです。
 *   実際の再割り当ての処理は、カーネル内のpceHeapRealloc()をそのまま使って、コードサイズが節約できます。
 * 
 * - これまでの修正パッチのコードは、将来もしPieceSystem本体を修正する際に、修正箇所の参考となりますので、
 *   コメントアウトして残しておくことにしました。
 */
static int fix_pceHeapRealloc(void* memp, unsigned long size);
static int (*bug_pceHeapRealloc)(void* memp, unsigned long size) = fix_pceHeapRealloc;
static int fix_pceHeapRealloc(void* memp, unsigned long size) {
	HEAPMEM* phm = (HEAPMEM*)memp - 1;
	unsigned long oldsize = (unsigned long)phm->chain - (unsigned long)memp;
	if(size > oldsize && size < oldsize + sizeof(HEAPMEM)/*8バイト*/) {
		size = oldsize + sizeof(HEAPMEM)/*8バイト*/;
	}
	return bug_pceHeapRealloc(memp, size);
}
static void bugfix_pceHeapRealloc() {
	bug_pceHeapRealloc = pceVectorSetKs(KSNO_HeapRealloc, bug_pceHeapRealloc);
}

/*****************************************************************************
 *	pceHeapAlloc()の性能改善
 *****************************************************************************/

// * Sun Dec 07 14:41:16 JST 2014 Naoyuki Sawa
// - pceHeapAlloc()の性能改善を行いました。
// - 「/usr/PIECE/sysdev/pcekn/heapman.c」のpceHeapAlloc()を、heapbroken(),SetHEAPMEM(),CombineFreeHEAPMEM(),AllocHEAPMEM()の処理も含めて実装しました。
//   元のpceHeapAlloc()は『ベストフィット方式』ですが、下記の実装は『ファーストフィット方式』としました。
//   一般的には、ベストフィット方式には利点が無く、ファーストフィット方式の方が速度もメモリ使用効率も良いと、言われているからです。
// - 当実装は、カーネルサービスベクタのpceHeapAlloc()を置き換えます。
//   従って、カーネルサービスベクタを経由してpceHeapAlloc()を呼び出す、全ての処理のメモリ割り当てアルゴリズムを変更します。
//   ただし、P/ECEカーネル内でpceHeapAlloc()にスタティックリンクしている処理だけは、置き換えることができません。
//   具体的には、pceFileCreate(),pceFileDelete(),pceFileRename(),pceFileLoad()です。
//   このことは、動作上の問題とはなりません。
//   元のpceHeapAlloc()も、当実装も、ヒープの取り扱い方は同じであるため、両者から確保・開放が行われても問題は無いからです。
#ifndef PIECE
static void* first_fit_pceHeapAlloc(unsigned long size0) {
	extern const unsigned char __START_DEFAULT_CODE[];			//┐
	const pceAPPHEAD* apphead = (const pceAPPHEAD*)__START_DEFAULT_CODE;	//├定型処理。詳細はclipgc.cのgc_init()の図解を参照してください。
	HEAPMEM* phm = (HEAPMEM*)(apphead->bss_end + apphead->stack_size);	//┘
	HEAPMEM *phm0, *phm1;
	//確保するサイズに、メモリブロック管理構造体のサイズを加え、ワードサイズの倍数に切り上げる。
	size0 = ((size0 + sizeof(HEAPMEM) + 3) & ~3);
	//メモリブロックを走査して…
	do {
		if(phm->mark != HEAPMEMMK) { DIE(); }	//ヒープが壊れている。
		phm0 = phm->chain;
		//空きメモリブロックを見つけたら…
		if(!phm->owner) {
			//このメモリブロックのサイズ(メモリブロック管理構造体のサイズ込み)を求める。
			unsigned long size1 = (unsigned long)phm0 - (unsigned long)phm;
			//確保するサイズ(メモリブロック管理構造体のサイズ込み)以上ならば…
			if(size1 >= size0) {
				//メモリブロックのサイズが、確保するサイズ(メモリブロック管理構造体のサイズ込み)と、分割した場合に増える管理構造体の合計よりも大きければ…
				if(size1 > (size0 + sizeof(HEAPMEM))) {
					//次以降のメモリブロックが空きブロックならば、分割したメモリブロックに結合する。
					phm1 = phm;
					do {
						phm1 = phm1->chain;
						if(phm1->mark != HEAPMEMMK) { DIE(); }	//ヒープが壊れている。
					} while(!phm1->owner);		//空きブロックでなければ抜ける。	HMO_SYSTEMに到達した場合もここで抜ける。
					//分割したメモリブロックを設定する。
					phm0 = (void*)phm + size0;	//(void*)への加算はgcc拡張
			/*要*/		phm0->mark  = HEAPMEMMK;
					phm0->owner = 0;
					phm0->chain = phm1;		//次以降のブロックで、最初に空きブロックでないブロックのアドレス。
				}
				//確保したメモリブロックを設定する。
			//不要	phm->mark  = HEAPMEMMK;
				phm->owner = HMO_DEFAULT_USER;
				phm->chain = phm0;			//上のifブロックを通らなかった場合は、phm0==phm->chainのままです。
				//確保したメモリブロックの、管理構造体の後のアドレスを返す。
				phm++;
				break;	//ここまで
			}
		}
	} while((phm = phm0));	//HMO_SYSTEMに到達したら!phmになって抜け、割り当て失敗としてNULLを返す。
	return   phm;
}
#else //PIECE
/*static*/ void* first_fit_pceHeapAlloc(unsigned long size0);
asm("
		.code
		.align		1
first_fit_pceHeapAlloc:
		xld.w		%r9, __START_DEFAULT_CODE+24	;//%r9  := &apphead->stack_size
		ld.w		%r10, [%r9]+			;//%r10 := stack_size
		ld.w		%r9, [%r9]			;//%r9  := bss_end
		add		%r12, 3				;//%r12 := size0' +=  3			(size0' := size0 - 8)	*anti-interlock*
		add		%r10, %r9			;//%r10 := phm := bss_end + stack_size
		and		%r12, -4			;//%r12 := size0' &= ~3
		xld.w		%r4, 0x1A431			;//%r4  := (HEAPMEMMK,HMO_DEFAULT_USER)
		ld.uh		%r5, %r4			;//%r5  := (HEAPMEMMK,0)
first_fit_pceHeapAlloc_L10:					;//do {
		;//%r4  := (HEAPMEMMK,HMO_DEFAULT_USER)
		;//%r5  := (HEAPMEMMK,0)
		;//%r10 := phm
		;//%r12 := size0'
		ld.w		%r9, [%r10]+			;//  %r9  := (mark,owner) = (phm->mark,phm->owner)
		ld.w		%r6, [%r10]+			;//  %r6  := phm0 = phm->chain, %r10 := phm + 8
		cmp		%r9, %r5			;//  if((mark,owner) == (HEAPMEMMK,0)) {
		jrne		first_fit_pceHeapAlloc_L30
		;//---------------------------------------------;//
		;//%r4  := (HEAPMEMMK,HMO_DEFAULT_USER)
		;//%r5  := (HEAPMEMMK,0)
		;//%r6  := phm0
		;//%r10 := phm + 8
		;//%r12 := size0'
		ld.w		%r7, %r6			;//    %r7  :=          phm0
		sub		%r7, %r10			;//    %r7  := size1' = phm0 - phm - 8	(size1' := size1 - 8)
		sub		%r7, %r12			;//    %r7  := size1' - size0'
		jrult		first_fit_pceHeapAlloc_L40	;//    if((size1' - size0') < 0) { goto L40 }
		add		%r12, %r10			;//    %r12 := phm + size0
		cmp		%r7, 8				;//    if((size1' - size0') > 8) {
		jrule.d	first_fit_pceHeapAlloc_L20
		sub		%r10, 8				;//      %r10 := phm						*delay*
		;//%r4  := (HEAPMEMMK,HMO_DEFAULT_USER)
		;//%r5  := (HEAPMEMMK,0)
		;//%r6  := phm0
		;//%r10 := phm
		;//%r12 := phm + size0
		ld.w		%r7, %r10			;//      %r7  := phm1 = phm
		ext		4				;//      do {
		 ld.w		%r7, [%r7]			;//        %r7  := phm1 = phm1->chain
		 ld.w		%r9, [%r7]			;//        %r9  := (mark,owner) = (phm1->mark,phm1->owner)	!INTERLOCK!
		 cmp		%r9, %r5			;//      } while((mark,owner) == (HEAPMEMMK,0))			!INTERLOCK!
		jreq		-4
		ld.uh		%r9, %r9			;//      %r9  := mark
		cmp		%r9, %r5			;//      if(mark != HEAPMEMMK) { DIE() }
		jrne		first_fit_pceHeapAlloc_DIE
		ld.w		%r6, %r12			;//      %r6  := phm0 = phm + size0
		ld.w		[%r12]+, %r5			;//      (phm0->mark,phm0->owner) = (HEAPMEMMK,0)
		ld.w		[%r12], %r7			;//      phm0->chain = phm1
first_fit_pceHeapAlloc_L20:					;//    }
		ld.w		[%r10]+, %r4			;//    (phm->mark,phm->owner) = (HEAPMEMMK,HMO_DEFAULT_USER)
		ld.w		[%r10]+, %r6			;//    phm->chain = phm0, %r10 := phm + 8
		ret						;//    return                     phm + 8
		;//---------------------------------------------;//  }
		;//%r4  := (HEAPMEMMK,HMO_DEFAULT_USER)
		;//%r5  := (HEAPMEMMK,0)
		;//%r6  := phm0
		;//%r9  := (mark,owner)
first_fit_pceHeapAlloc_L30:
		ld.uh		%r9, %r9			;//  %r9  :=  mark
		cmp		%r9, %r5			;//  if(mark != HEAPMEMMK) { DIE() }
		jrne		first_fit_pceHeapAlloc_DIE
first_fit_pceHeapAlloc_L40:
		cmp		%r6, 0
		jrne.d		first_fit_pceHeapAlloc_L10	;//} while(  phm)
		ld.w		%r10, %r6			;//  %r10 := phm = phm0						*delay*
		ret						;//return    phm
");
static void __attribute__((noreturn,unused))/*asmブロックから参照*/ first_fit_pceHeapAlloc_DIE() { DIE(); }
#endif//PIECE
//一回呼ぶとフックし、もう一度呼ぶと元に戻します。
static void hook_unhook_first_fit_pceHeapAlloc() {
	static void* (*save_pceHeapAlloc)(unsigned long size) = first_fit_pceHeapAlloc;
	save_pceHeapAlloc = (PCEKSENT)pceVectorSetKs(KSNO_HeapAlloc, save_pceHeapAlloc);
}

/****************************************************************************
 *	割り込みレベル設定
 ****************************************************************************/

#ifndef PIECE
//指定された割り込みレベルが、現在の割り込みレベル超過ならば、割り込みレベルを変更し、変更前の%psrを返す。
//指定された割り込みレベルが、現在の割り込みレベル以下ならば、割り込みレベルを変更せず、現在の%psrを返す。
int EnterIL(int il) {	//戻り値の型を'struct psr'型にすると、関数の呼び出し方法が違ってくるので不可です。
	struct psr psr = get_psr();
	if(il > psr.il) {		//現在の割り込みレベルが既に指定されたレベル以上ならば何もしない。
		struct psr tmp = psr;
		tmp.il = il;
		set_psr(tmp);
	}
	return *(int*)&psr;
}
//%psrを設定する。直接set_psr()を呼んでも同じだが、EnterIL()と対であることを強調するため当関数を作成した。
void LeaveIL(int psr) {
	set_psr(*(struct psr*)&psr);
}
#else //PIECE
int EnterIL(int il);
void LeaveIL(int psr);
asm("
		.code
		.align		1
		.global		EnterIL
		.global		LeaveIL
EnterIL:
		sla		%r12, 8			;//%r12 :=       00000000 00000000 0000xxxx 00000000
		ld.w		%r10, %psr		;//%r10 := psr = 00000000 00000000 0000yyyy OS0ICVZN		//%psr[31:12]は0固定と見なせる｡33000Core-J.pdf｢2.1.3 ﾌﾟﾛｾｯｻｽﾃｰﾀｽﾚｼﾞｽﾀ(PSR)｣:'-'は未使用ﾋﾞｯﾄを示します｡書き込みは無効で､読み出しは常に0となります｡
		cmp		%r12, %r10		;//if(xxxx 00000000 > yyyy OS0ICVZN) {
		jrle		4			;//  
		 ld.ub		%r9, %r10		;//  %r9  :=     00000000 00000000 00000000 OS0ICVZN
		 or		%r12, %r9		;//  %r12 :=     00000000 00000000 0000xxxx OS0ICVZN
LeaveIL:						;//}
		 ld.w		%psr, %r12		;//%psr :=       00000000 00000000 0000xxxx OS0ICVZN
		ret					;//return  psr							//EnterILの場合､変更前の%psrを返す｡LeaveILの場合､ﾀﾞﾐｰ｡
");
#endif//PIECE

/****************************************************************************
 *	タイマ割り込み処理中の多重割り込み許可
 ****************************************************************************/

/* * Wed Dec 24 19:14:00 JST 2014 Naoyuki Sawa
 * - 「タイマ割り込み処理中の多重割り込み許可」を追加しました。
 * - これまでは、タイマ割り込み処理中は(IL=1,IE=0)でミキシング割り込み(IL=7)が発生できず、タイマ割り込みでGCを起動した場合(cliptcl.cのコメント参照)等に音飛びが発生する問題が有りました。
 *   今後は、タイマ割り込み処理中(IL=1,IE=1)も、ミキシング割り込み(IL=7)が発生できるので、音飛びが発生しづらくなります。
 * - ただし、ストリームコールバック(IL=1)は発生できないので、タイマ割り込み処理が長時間かかる場合は、やはり音飛びが発生します。				┐
 *   この問題は、ストリームコールバックを(IL=2)にすることにより回避できます。(ただし、ストリームコールバックがメモリAPIを使用しない場合に限る。)	├→2014/12/25追記:stream_play()の第3引数に0を指定すればストリームコールバックを(IL=2)にできるようになりました。詳細はclippce.hのstream_play()の説明を参照してください。
 *   詳細は、clippce.hの「割り込みレベル設定」の'Mon Dec 22 21:06:44 JST 2014'のコメントを参照してください。						┘
 * - 今回の変更により、P/ECEカーネルのtimer_isr()(/usr/PIECE/sysdev/pcekn/timer.c)全体が、多重割り込み許可で動作するようになります。
 *   これはP/ECEカーネルが想定していない動作ですが、検討した限りでは問題無いと思います。理由は以下の通りです:
 *   ・timer_isr()から呼び出される処理は、コンテキスト切り替えとタイマコールバック処理のみである。P/ECEカーネル自身は危険な処理を行っていない。
 *   ・コンテキスト切り替えを実行するアプリケーション処理中(clipidle.cのidle_context_switcher())に、上位レベルの割り込みが発生しても、それによって生じる影響は無いと思う。
 *   ・タイマコールバック処理中に、発生すると問題が生じるような、上位の割り込み処理は無いと思う。
 *   ・タイマコールバック処理中に発生すると問題が生じる可能性の有る(=アプリケーションがフックしている)、タイマ,及び,USB割り込みはIL=1に変更済みであり、今後もタイマコールバック処理中には発生しない。
 *   以上の理由で【安全】と判断しました。引続き検証を行い、もし問題が生じた場合は「タイマ割り込み処理中の多重割り込み許可」を削除してください。
 */

/*static*/ void new_timer_isr();
static PCEKSENT old_timer_isr = new_timer_isr;
//一回呼ぶとフックし、もう一度呼ぶと元に戻します。
static void hook_unhook_timer() {
ENTER_CS;//{{2015/02/01追加:hook_unhook_timer()のバグが原因でアプリケーション起動時に数%の確率でハングアップしていた問題を修正しました。pceVectorSetTrap()の戻り値をold_timer_isrに格納する前に割込みが掛かるとnew_timer_isrが未格納のold_timer_isrを参照していたバグでした。正しくはpceVectorSetTrap()の戻り値をold_timer_isrに格納するまで割り込み禁止にすべきでした。}}
	old_timer_isr = pceVectorSetTrap(TRAP_16TU0, old_timer_isr);
LEAVE_CS;//{{2015/02/01追加:hook_unhook_timer()のバグが原因でアプリケーション起動時に数%の確率でハングアップしていた問題を修正しました。pceVectorSetTrap()の戻り値をold_timer_isrに格納する前に割込みが掛かるとnew_timer_isrが未格納のold_timer_isrを参照していたバグでした。正しくはpceVectorSetTrap()の戻り値をold_timer_isrに格納するまで割り込み禁止にすべきでした。}}
}
//「new_timer_isr⇒old_timer_isr呼び出し⇒old_timer_isrがreti⇒new_timer_isrがreti」という方法は取れません。(未検証。可能かも知れないが危険?)
//理由は「/usr/PIECE/sysdev/pcekn/timer.c」のtimer_isr(),及び,pceTimerSetContextSwitcher()が、スタックフレームの構成を仕様化しているからです。
//そこで、timer_isr()から見て*厳密に*割り込み発生時のスタックフレームに一致するよう、以下の処理方法にしました。詳細はコードを参照してください。
asm("
		.code
		.align		1
new_timer_isr:
		;//[%sp+ 0] := %psr
		;//[%sp+ 4] := %pc
		pushn		%r2
		;//[%sp+ 0] := %r0
		;//[%sp+ 4] := -
		;//[%sp+ 8] := -
		;//[%sp+12] := %psr
		;//[%sp+16] := %pc
		ld.w		%r0, %psr			;//%r0     := %psr
		or		%r0, 0x10			;//%r0     := %psr|IE
		xld.w		[%sp+4], %r0			;//[%sp+4] := %psr|IE
		xld.w		%r0, [old_timer_isr]		;//%r0     := old_timer_isr
		xld.w		[%sp+8], %r0			;//[%sp+8] := old_timer_isr	!INTERLOCK!
		;//[%sp+ 0] := %r0
		;//[%sp+ 4] := %psr|IE
		;//[%sp+ 8] := old_timer_isr
		;//[%sp+12] := %psr
		;//[%sp+16] := %pc
		popn		%r0
		;//[%sp+ 0] := %psr|IE ───┬ retiが消費する。
		;//[%sp+ 4] := old_timer_isr ┘
		;//[%sp+ 8] := %psr ┬ old_timer_isrが消費する。
		;//[%sp+12] := %pc  ┘
		reti						;//      goto old_timer_isr
");

/****************************************************************************
 *	8ビットプログラマブルタイマ割り込みの一元管理
 ****************************************************************************/
typedef struct _ST_8TU {
	void (*callback[3+1])();
	//8-bit timer clock selection register
	uint8_t save_pCLKSEL_T8;	//8-bit timer clock select register (0x40146)
//P/ECEカーネルが設定を変更するのは8-bit timer 1のみです(/usr/PIECE/sysdev/pcekn/powerman.cのpcePowerEnterStandby()参照)。8-bit timer 2,3,4の設定を退避/復元する不要は有りません。
	//8-bit timer clock control register
	uint8_t save_pCLKCTL_T8_01;	//8-bit timer 0/1 clock control register (0x4014d)
//不要	uint8_t save_pCLKCTL_T8_23;	//8-bit timer 2/3 clock control register (0x4014e)
	//8-bit timer control register
//不要	uint8_t save_pT8_RLD0;		//8-bit timer 0 reload data register (0x40161)
	uint8_t save_pT8_RLD1;		//8-bit timer 1 reload data register (0x40165)
//不要	uint8_t save_pT8_RLD2;		//8-bit timer 2 reload data register (0x40169)
//不要	uint8_t save_pT8_RLD3;		//8-bit timer 3 reload data register (0x4016d)
	//8-bit timer control register
//不要	uint8_t save_pT8_CTL0;		//8-bit timer 0 clock control register (0x40160)
	uint8_t save_pT8_CTL1;		//8-bit timer 1 clock control register (0x40164)
//不要	uint8_t save_pT8_CTL2;		//8-bit timer 2 clock control register (0x40168)
//不要	uint8_t save_pT8_CTL3;		//8-bit timer 3 clock control register (0x4016c)
	//8-bit timer interrupt enable register
	uint8_t save_pINT_E8T;		//8-bit timer interrupt enable register (0x40275)
} ST_8TU;
static ST_8TU st8TU;
/*--------------------------------------------------------------------------*/
#ifndef PIECE
/*static*/ void internal_8tu_isr();
asm("
		.code
		.align		1
internal_8tu_isr:
		pushn		%r15
		ld.w		%r0, %alr
		ld.w		%r1, %ahr
	;//不要	pushn		%r1			;//{{2014/12/27削除:割り込みサービスルーチンの無駄なレジスタ保存を削除しました。C関数が呼出し規約に従い%r0～%r3を保存するので(%alr,%ahr)は(%r0,%r1)に入れたままで大丈夫です。}}
		xcall		_internal_8tu_isr
	;//不要	popn		%r1			;//{{2014/12/27削除:割り込みサービスルーチンの無駄なレジスタ保存を削除しました。C関数が呼出し規約に従い%r0～%r3を保存するので(%alr,%ahr)は(%r0,%r1)に入れたままで大丈夫です。}}
		ld.w		%ahr, %r1
		ld.w		%alr, %r0
		popn		%r15
		reti
");
static void __attribute__((unused))/*asmブロックから参照*/ _internal_8tu_isr() {
	int flag;
	void (**pcallback)(), (*callback)();
	bRST_RESET_RSTONLY = 1;	//割り込み要因フラグのリセット方式を、リセットオンリー方式に切り替える。
	flag = pINT_F8T;	//割り込み要因フラグを取得する。
	pINT_F8T = flag;	//割り込み要因フラグをリセットする。
	bRST_RESET_RSTONLY = 0;	//割り込み要因フラグのリセット方式を、リード/ライト方式に戻す。
	ENABLE;			//上位プライオリティの多重割り込みを受け付ける。			←←←←←【重要】コールバックは多重割り込み許可で呼び出すことにしました。
	//リセットした割り込み要因フラグに対応するコールバックを呼び出す。
	pcallback = st8TU.callback;
	do {
		if((callback = *pcallback++) && (flag & 1)) { (*callback)(); }
	} while(flag >>= 1);
}
#else //PIECE
// * Sat Dec 27 17:13:55 JST 2014 Naoyuki Sawa
// - internal_8tu_isr()と_internal_8tu_isr()を統合して、internal_8tu_isr()一関数にし、最適化しました。
/*static*/ void internal_8tu_isr();
asm("
		.code
		.align		1
internal_8tu_isr:
		pushn		%r15
		ld.w		%r0, %alr
		ld.w		%r1, %ahr
	;//不要	pushn		%r1				;//{{2014/12/27削除:割り込みサービスルーチンの無駄なレジスタ保存を削除しました。C関数が呼出し規約に従い%r0～%r3を保存するので(%alr,%ahr)は(%r0,%r1)に入れたままで大丈夫です。}}
		;//%r0  := %alr
		;//%r1  := %ahr
		;//---------------------------------------------;//
	;//{{①素直な実装。(12命令)
	;//	xbset		[0x4029f], 0			;//bRST_RESET_RSTONLY = 1
	;//	xld.ub		%r2, [0x40285]			;//%r2  := flag = pINT_F8T
	;//	xld.b		[0x40285], %r2			;//pINT_F8T = flag
	;//	xbclr		[0x4029f], 0			;//bRST_RESET_RSTONLY = 0
	;//↓②命令数を節約した。(9命令)
	;//	xld.w		%r3, 0x4029f			;//%r3  := 0x4029f
	;//	bset		[%r3], 0			;//bRST_RESET_RSTONLY = 1
	;//	xsub		%r4, %r3, 26			;//%r4  := 0x40285
	;//	ld.ub		%r2, [%r4]			;//%r2  := flag = pINT_F8T
	;//	ld.b		[%r4], %r2			;//pINT_F8T = flag			!INTERLOCK!
	;//	bclr		[%r3], 0			;//bRST_RESET_RSTONLY = 0
	;//↓③インターロックを回避した。(9命令)
		xld.w		%r3, 0x4029f			;//%r3  := 0x4029f
	    ;//	bset		[%r3], 0			;//bRST_RESET_RSTONLY = 1	─┐
		xsub		%r4, %r3, 26			;//%r4  := 0x40285		　│
		ld.ub		%r2, [%r4]			;//%r2  := flag = pINT_F8T	　│	'割り込み要因フラグのリセット方式は、割り込み要因フラグの取得には影響しない'事を利用して、割り込み要因フラグのリセット方式の変更を遅らせ、インターロックを回避した。
		bset		[%r3], 0			;//bRST_RESET_RSTONLY = 1	←┘	*anti-interlock*
		ld.b		[%r4], %r2			;//pINT_F8T = flag
		bclr		[%r3], 0			;//bRST_RESET_RSTONLY = 0
	;//}}
		ld.w		%r9, %psr			;//┐
		or		%r9, 0x10			;//├ENABLE
		ld.w		%psr, %r9			;//┘
		xld.w		%r3, st8TU+0			;//%r3  := pcallback = st8TU.callback
		;//%r0  := %alr
		;//%r1  := %ahr
		;//%r2  := flag
		;//%r3  := pcallback
internal_8tu_isr_LOOP:						;//do {
		ld.w		%r4, [%r3]+			;//  %r4  := callback = *pcallback++
		cmp		%r4, 0				;//  if(callback) {
		jreq		internal_8tu_isr_NEXT		;//    
		xand		%r9, %r2, 1			;//    if(flag & 1) {
		jreq		internal_8tu_isr_NEXT		;//      
		call		%r4				;//      (*callback)()
internal_8tu_isr_NEXT:						;//  } }
		sra		%r2, 1				;//  %r2  := flag >>= 1
		jrne		internal_8tu_isr_LOOP		;//while(flag)
		;//---------------------------------------------;//
		;//%r0  := %alr
		;//%r1  := %ahr
	;//不要	popn		%r1				;//{{2014/12/27削除:割り込みサービスルーチンの無駄なレジスタ保存を削除しました。C関数が呼出し規約に従い%r0～%r3を保存するので(%alr,%ahr)は(%r0,%r1)に入れたままで大丈夫です。}}
		ld.w		%ahr, %r1
		ld.w		%alr, %r0
		popn		%r15
		reti
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
static void init_8tu_interrupt() {
	int iCh;
//不要	memset(&st8TU, 0, sizeof st8TU);
	//TRAP_8TU0～TRAP_8TU3に共通の割り込みサービスルーチンをインストールしておく。
	for(iCh = 0; iCh <= 3; iCh++) {
		pceVectorSetTrap(TRAP_8TU0 + iCh, internal_8tu_isr);
	}
	//割り込みプライオリティを設定する。
	//8ビットプログラマブルタイマ割り込みのプライオリティは、ミキシング割り込み(IL=7)よりも下で、それ以外の割り込みよりも上とした。
	//判断理由は、以下の通りです:
	// - 8ビットプログラマブルタイマ割り込みは「高速切替・16階調表示」に利用するので、ある程度高いプライオリティにする必要がある。
	// - ミキシング割り込み(IL=7)は少しでも遅れるとすぐ音飛びが発生するので、それより下の割り込みプライオリティにする必要がある。
	// - ストリームコールバック(IL=1or2)はミキシング割り込み数回分をまとめて実行する余裕があるので、それより上でも大丈夫だろう。
	// - 従って(IL=3～6)が適切である。今回は(IL=6)とすることにした。実際に使って見て検証し(IL=3～6)の範囲で調整する予定である。
	bINT_P8T_PSIO0_P8TM = 6;	//実際に使って見て検証し(IL=3～6)の範囲で調整する予定である。
}
/*--------------------------------------------------------------------------*/
static void free_8tu_interrupt() {
	pINT_E8T = 0;	//全チャネルの割り込みイネーブルフラグをクリアする。
	//TRAP_8TU0～TRAP_8TU3は復元不要。P/ECEカーネルは8ビットプログラマブルタイマ割り込みを使っていないから。
	//bINT_P8T_PSIO0_P8TM は復元不要。P/ECEカーネルは8ビットプログラマブルタイマ割り込みを使っていないから。
}
/*--------------------------------------------------------------------------*/
void install_8tu_interrupt_callback(int iCh, void (*callback)()) {
	int flag;
	if((unsigned)iCh > 3) { DIE(); }
	flag = (1<<iCh);		//割り込みイネーブルフラグと割り込み要因フラグのビット位置は共通である。
ENTER_CS;
	if((st8TU.callback[iCh] = callback)) {
		bRST_RESET_RSTONLY = 1;	//割り込み要因フラグのリセット方式を、リセットオンリー方式に切り替える。
		pINT_F8T  =  flag;	//割り込み要因フラグをリセットする。
		bRST_RESET_RSTONLY = 0;	//割り込み要因フラグのリセット方式を、リード/ライト方式に戻す。
		pINT_E8T |=  flag;	//割り込みイネーブルフラグをセットする。
	} else {
		pINT_E8T &= ~flag;	//割り込みイネーブルフラグをクリアする。
	}
LEAVE_CS;
}
/*--------------------------------------------------------------------------*/
static void suspend_8tu_interrupt() {
	//8ビットプログラマブルタイマの設定を退避する。
	//8-bit timer clock selection register
	st8TU.save_pCLKSEL_T8		= pCLKSEL_T8;			//8-bit timer clock select register (0x40146)
	//8-bit timer clock control register
	st8TU.save_pCLKCTL_T8_01	= pCLKCTL_T8_01;		//8-bit timer 0/1 clock control register (0x4014d)
//不要	st8TU.save_pCLKCTL_T8_23	= pCLKCTL_T8_23;		//8-bit timer 2/3 clock control register (0x4014e)
	//8-bit timer control register
//不要	st8TU.save_pT8_RLD0		= pT8_RLD0;			//8-bit timer 0 reload data register (0x40161)
	st8TU.save_pT8_RLD1		= pT8_RLD1;			//8-bit timer 1 reload data register (0x40165)
//不要	st8TU.save_pT8_RLD2		= pT8_RLD2;			//8-bit timer 2 reload data register (0x40169)
//不要	st8TU.save_pT8_RLD3		= pT8_RLD3;			//8-bit timer 3 reload data register (0x4016d)
	//8-bit timer control register
//不要	st8TU.save_pT8_CTL0		= pT8_CTL0;			//8-bit timer 0 clock control register (0x40160)
	st8TU.save_pT8_CTL1		= pT8_CTL1;			//8-bit timer 1 clock control register (0x40164)
//不要	st8TU.save_pT8_CTL2		= pT8_CTL2;			//8-bit timer 2 clock control register (0x40168)
//不要	st8TU.save_pT8_CTL3		= pT8_CTL3;			//8-bit timer 3 clock control register (0x4016c)
	//8-bit timer interrupt enable register
	st8TU.save_pINT_E8T		= pINT_E8T;			//8-bit timer interrupt enable register (0x40275)
	//8ビットプログラマブルタイマ割り込みを停止する。
	//8-bit timer interrupt enable register
	pINT_E8T			= 0;				//8-bit timer interrupt enable register (0x40275)
}
/*--------------------------------------------------------------------------*/
static void resume_8tu_interrupt() {
	//8ビットプログラマブルタイマの設定を復元する。
	//8-bit timer clock selection register
	pCLKSEL_T8			= st8TU.save_pCLKSEL_T8;	//8-bit timer clock select register (0x40146)
	//8-bit timer clock control register
	pCLKCTL_T8_01			= st8TU.save_pCLKCTL_T8_01;	//8-bit timer 0/1 clock control register (0x4014d)
//不要	pCLKCTL_T8_23			= st8TU.save_pCLKCTL_T8_23;	//8-bit timer 2/3 clock control register (0x4014e)
	//8-bit timer control register
//不要	pT8_RLD0			= st8TU.save_pT8_RLD0;		//8-bit timer 0 reload data register (0x40161)
	pT8_RLD1			= st8TU.save_pT8_RLD1;		//8-bit timer 1 reload data register (0x40165)
//不要	pT8_RLD2			= st8TU.save_pT8_RLD2;		//8-bit timer 2 reload data register (0x40169)
//不要	pT8_RLD3			= st8TU.save_pT8_RLD3;		//8-bit timer 3 reload data register (0x4016d)
	//8-bit timer control register
//不要	pT8_CTL0			= st8TU.save_pT8_CTL0;		//8-bit timer 0 clock control register (0x40160)
	pT8_CTL1			= st8TU.save_pT8_CTL1;		//8-bit timer 1 clock control register (0x40164)
//不要	pT8_CTL2			= st8TU.save_pT8_CTL2;		//8-bit timer 2 clock control register (0x40168)
//不要	pT8_CTL3			= st8TU.save_pT8_CTL3;		//8-bit timer 3 clock control register (0x4016c)
	//8-bit timer interrupt enable register
	pINT_E8T			= st8TU.save_pINT_E8T;		//8-bit timer interrupt enable register (0x40275)
}

/****************************************************************************
 *	NMIサービスルーチンの高速化
 ****************************************************************************/
#if 0
// * Wed Dec 31 00:00:35 JST 2014 Naoyuki Sawa
// - P/ECEカーネルのNMIサービスルーチンをフックして高速化するルーチンを作成しました。
//   効果が薄いのでこの方法は使わないことにしましたが、コメントとして残しておきます。
//   具体的には、以下のとおりです。
// - P/ECEカーネル1.20のNMIサービスルーチンは無駄が有り、少し高速化できます。
//   それに加えて、高速RAMの未使用領域に転送して実行することで、さらに高速化できると考えました。
//   1ms割り込みで頻繁に実行されるので、アプリケーションへの影響もある程度有ると思ったからです。
// - しかし、実際に効果を計測してみると、わずか0.05%程度の高速化でした。
//   効果が薄く、フックルーチンのメモリ消費の方が無駄なので、この方法は使わないことにしました。
// - 動作自体は正常に働くことを確認したので、コメント化して残しておくことにします。
//   「カーネル変数のアドレスを推測する例」として、今後、応用できるかも知れません。
/*--------------------------------------------------------------------------*/
//□P/ECEカーネル1.20の実装(\usr\PIECE\sysdev\pcekn\timer.c)
//nmi_isr:
//		pushn		%r1			;//+ 0,2
//		ext		     ClockTicks@h	;//+ 2,2	ClockTicksは低位アドレスなのでext 1個で足りるのに、2個に展開されてしまう無駄が有る。
//		ext		     ClockTicks@m	;//+ 4,2
//		ld.w		%r1, ClockTicks@l	;//+ 6,2
//		ld.w		%r0, [%r1]		;//+ 8,2
//		add		%r0, 1			;//+10,2	!INTERLOCK!
//		ld.w		[%r1], %r0		;//+12,2
//		popn		%r1			;//+14,2
//		reti					;//+16,2
//							;//=18
//↓最適化↓
//□レジスタ退避数と命令数を節約した実装
asm("
		.code
		.align		1
nmi_isr:
		pushn		%r0			;//+ 0,2
		ext		0			;//+ 2,2	hook_unhook_nmi()が初回に呼び出された時に、この箇所をext ClockTicks@alに書き換える。
		ld.w		%r0, [%r8]		;//+ 4,2
		add		%r0, 1			;//+ 6,2	!INTERLOCK!
		ext		0			;//+ 8,2	hook_unhook_nmi()が初回に呼び出された時に、この箇所をext ClockTicks@alに書き換える。
		ld.w		[%r8], %r0		;//+10,2
		popn		%r0			;//+12,2
		reti					;//+14,2
							;//=16
");
/*--------------------------------------------------------------------------*/
//一回呼ぶとフックし、もう一度呼ぶと元に戻します。
static void hook_unhook_nmi() {
	static PCEKSENT old_nmi_isr;
	if(!old_nmi_isr) {	//初回ならば…
		volatile unsigned* pClockTicks = NULL;
		//カーネル変数ClockTicksのアドレスを推測する。
		{
			volatile unsigned* const __START_PCEKN_BSS = (void*)0x520;	//\usr\PIECE\sysdev\pcekn\vector.hで定義されているベクタ領域の直後から
			volatile unsigned* const __END_PCEKN_BSS   = (void*)0xe00;	//\usr\PIECE\sysdev\pcekn\pcekn.cmで定義されているFRAM4領域の直前まで
			int retry = 100/*調整可*/;
			do {
				volatile unsigned* p = __START_PCEKN_BSS;
				do {
					unsigned t1, t2, t3;
					do {
						t1 = pceTimerGetCount();	//①APIで時刻を取得1
						t2 = *p;			//②メモリ参照で時刻(?)を取得
						t3 = pceTimerGetCount();	//③APIで時刻を取得2
					} while(t1 > t3);			//①と③がunsignedの最大値をまたいでいたらやり直す。
					if((t1 <= t2) && (t2 <= t3)) {		//①≦②≦③ならば、このアドレスがClockTicksである可能性が有る。
						if(pClockTicks) {		//候補アドレスが二箇所以上見つかったら直す。
							pClockTicks = NULL;
							break;
						}
						pClockTicks = p;		//候補アドレスを格納する。
					}
				} while(++p < __END_PCEKN_BSS);
				if(pClockTicks) { break; };			//唯一の候補アドレスが見つかったら抜ける。
			} while(--retry);
			if(!pClockTicks) { DIE(); }				//リトライ回数の限界以内に唯一の候補アドレスが見つからなければエラーとする。実験したところリトライ3回以内ぐらいで成功するようです。
		}
		//nmi_isrを作成して、高速RAMに転送する。
		{
			extern unsigned short nmi_isr[];
			nmi_isr[1] |= (int)pClockTicks;
			nmi_isr[4] |= (int)pClockTicks;
			old_nmi_isr = memcpy((void*)0x10, nmi_isr, 16);	//高速RAMの未使用領域0x10～0x1Fに転送して実行する。
		}
	}
	old_nmi_isr = pceVectorSetTrap(TRAP_NMI, old_nmi_isr);
}
#endif

/****************************************************************************
 *	割り込みサービスルーチンを定義するマクロ
 ****************************************************************************/

//ISR_DEF()マクロの内部で使用します。明示的にisr_callを呼ぶ必要は有りません。
asm("
		.code
		.align		1
		.global		isr_call
isr_call:
		;//[%sp+ 0] := retptr
		;//[%sp+ 1] := %pc
		;//[%sp+ 2] := %psr
		;//[retptr] := isrptr
		pushn		%r15
		;//[%sp+ 0] := %r0
		;//         …
		;//[%sp+15] := %r15
		;//[%sp+16] := retptr
		;//[%sp+17] := %psr
		;//[%sp+18] := %pc
		;//[retptr] := isrptr
		ld.w		%r9, [%sp+16]		;//%r9  := retptr
		ld.w		%r9, [%r9]		;//%r9  := isrptr	!INTERLOCK!
		ld.w		%r0, %alr		;//%r0  := %alr		*anti-interlock*
		call.d		%r9
		ld.w		%r1, %ahr		;//%r1  := %ahr		*delay*
		ld.w		%ahr, %r1		;//%ahr := %r1
		ld.w		%alr, %r0		;//%alr := %r0
		;//[%sp+ 0] := %r0
		;//         …
		;//[%sp+15] := %r15
		;//[%sp+16] := retptr
		;//[%sp+17] := %psr
		;//[%sp+18] := %pc
		popn		%r15
		;//[%sp+ 0] := retptr
		;//[%sp+ 1] := %pc
		;//[%sp+ 2] := %psr
		add		%sp, 1
		;//[%sp+ 0] := %pc
		;//[%sp+ 1] := %psr
		reti
");

//{{2016/11/17追加:assert()マクロの実装を変更しました。(変更したファイルは、clippce.cとinclude/assert.hです。)
/****************************************************************************
 *	assert()マクロのサポートルーチン
 ****************************************************************************/
// * Thu Nov 17 21:56:29 JST 2016 Naoyuki Sawa
// - assert()マクロの実装を変更しました。(変更したファイルは、clippce.cとinclude/assert.hです。)
//   変更した理由は、assert()マクロを有効にして(NDEBUGを定義せずに)Nuklearをビルドすると、展開コードのメモリ消費量が多過ぎて、メモリが足りない問題が生じたからです。
//   Nuklearに限らず、これまではメモリ消費量を気にして、なかなかassert()マクロを有効に(NDEBUGを未定義に)出来なかったのですが、今後は(比較的)気軽に使えると思います。
//   変更内容の詳細は、以下の通りです。
// - 変更前の「#define assert(f) ((void)((f)||(die3(__FILE__,__LINE__,#f),0)))」は、おおよそ以下のように展開されていました:
//	│	cmp	%r??,0					;//+ 0,2
//	│	jrne	__Lxx					;//+ 2,2
//	│	xld.w	%r12,ファイル名の文字列のアドレス	;//+ 4,6	アドレスの代入なので常に3命令に展開される。
//	│	xld.w	%r13,行番号				;//+10,4	行番号の値にもよるが大抵2命令に展開される。
//	│	xld.w	%r14,式の文字列のアドレス		;//+14,6	アドレスの代入なので常に3命令に展開される。
//	│	xcall	die3					;//+20,4
//	│__Lxx:						;//=24
//   上記の展開コードに加えて、式の文字列も表示するために保持していたので、assert()は非常に多くのメモリを消費していました。
// - 変更後は、アプリケーションが式を判定してdie3()を呼び出すのではなく、式の値とインラインパラメータを引数として'int 0'でnew_assert_isr()を呼び出すようにしました。
//   式の値を判定する処理を、アプリケーションの個々の箇所ではなく、new_assert_isr()に一元化した事によって、コードサイズが削減出来ました。
//   また、呼び出し命令を'xcall'ではなく'int'を用いた事によって、個々の箇所で1命令削減出来ました。
//   ファイル名の文字列のアドレスと行番号を、レジスタに入れて呼び出すのではなく、インラインパラメータで指定する事によって、個々の箇所で1～2命令削減出来ました。
//   変更後のassert()マクロの展開コードは、%r9以外のレジスタを破壊しないので、アプリケーションがレジスタを退避する必要が減り、コードサイズ削減の効果が期待出来ます。
//   変更後は、エラー画面に式の文字列は表示しなくなったので、式の文字列を保持する必要がなくなり、メモリの消費が低減出来ました。
//   エラー画面に式の文字列を表示しなくても、ファイル名と行番号だけで、アサートした箇所は特定出来て、実用上問題無いと思います。
// - 尚、ソフトウェア例外0～3番の内、P/ECEカーネルが1番を使っているので、アプリケーションは0番,2番,3番のどれでも使用出来ました。
//   今回、assert()専用として、0番を使用する事にしました。
//   2番,3番は未使用なので、今後、別の用途に使用可能です。
// - 今回の変更によって、clipdie.cのdie3()は使わなくなったのですが、今後、何か別の用途で使う可能性も有るので、die3()も削除せずに残しておく事にしました。
// * Fri Dec 02 22:30:56 JST 2016 Naoyuki Sawa
// - assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
//   変更した理由は、die2()もソフトウェア例外を使用するように変更したからです。
//   前回コメントを書いた通り、ソフトウェア例外0～3番の内P/ECEカーネルが1番を使っており、これまではassert()が0番を使っていたので、0番,1番が使用中、2番,3番が空きとなっていました。
//   今回、die2()もソフトウェア例外を使用するため、もう一つソフトウェア例外番号を消費するようになりました。
//   assert()が0番のまま、die2()が2番か3番を使用しても良かったのですが、assert()とdie2()は連続した番号を使用する方が分かり易いと思ったので、assert()の番号も変更し、die2()が2番,assert()が3番を使用するようにしました。
//   結果として、ソフトウェア例外0～3番の内、P/ECEカーネルが1番、当ライブラリがdie2()用に2番,assert()用に3番を使うようになり、空き番号(=アプリケーション使用可)は0番となりました。
//   番号には特に意味は無いのですが、覚え方としては、「die'2'なのでint'2'」,「assert()は前回変更するまではdie'3'を使っていたのでint'3'」と関連付けて覚えれば、覚え易いと思います。
// - 今回、die2()もソフトウェア例外を使用するように変更した理由は、メモリ節約のためです。
//   DIE()マクロが展開されると、以下のようなコードが生成されます。
//	│	xld.w	%r12,ファイル名の文字列のアドレス	;//+ 0, 6	アドレスの代入なので常に3命令に展開される。
//	│	xld.w	%r13,行番号				;//+ 6, 4	行番号の値にもよるが大抵2命令に展開される。
//	│	xcall	die2					;//+10, 4
//	│							;//=14
//   DIE()マクロは、多くの箇所で使用しているので、これをメモリ節約するのは効果が高いと思いました。
//   メモリ節約した方法は、前回、assert()のメモリ節約で行ったのと、同じ方法です。
//   アライメントによっては、暗黙のnopが生成され得る事についても、前回のassert()と同様なので、詳細は、前回の実装部のコメントを参照して下さい。
//	│	int	2					;//+ 0, 2│+ 0, 2
//	│	[(暗黙のnop)]					;//      │+ 2, 2
//	│	.word	ファイル名の文字列のアドレス		;//+ 2, 4│+ 4, 4
//	│	.half	行番号(0～65535)			;//+ 6, 2│+ 8, 2
//	│							;//= 8   │=10
//   上記の通り、一か所当たり、6バイト,又は,4バイトの節約となります。
// - 実際にどれぐらい、メモリ節約の効果が有るかを確認しました。
//   現時点の、/clip/test.pce 雛形プロジェクトの最小構成で、BSS込みの全体の容量が、変更前(1105992バイト)⇒変更後(1105560バイト)となり、432バイトの節約です。
//   最小構成でなく、もっと多くのモジュールがリンクされた場合は、もっとメモリ節約の効果が高くなるはずなので、それなりにメモリ節約の効果は高いと思います。
// - ■■■■■重要(ここから)■■■■■重要(ここから)■■■■■重要(ここから)■■■■■重要(ここから)■■■■■重要(ここから)■■■■■重要(ここから)■■■■■重要(ここから)■■■■■重要(ここから)■■■■■
//   前回のassert()の変更では、include/assert.hのassert()マクロ自体を変更して、インラインアセンブラでソフトウェア例外のコードを生成するようにしていました。
//   しかし、今回のDIE()の変更では、DIE()マクロ自体を変更して、ソフトウェア例外のコードを生成するようには出来ませんでした。
//   従って今回は、clipdie.hのDIE()マクロの定義は、変更していません。
//   DIE()マクロ自体はこれまで通りdie2()の呼び出しに展開され、Cコンパイラもこれまで通りdie2へのコールを行うアセンブラコード(.ps)を生成します。
//   今回変更した方法は、/clip/tool/dpcc33/ を変更して、dpcc33.exeによって、.ps内のdie2へのコールを、ソフトウェア例外(int 2)に変換するようにしました。
//   具体的には、/clip/tool/dpcc33/app.c の、同日の変更箇所を参照して下さい。
//   なぜDIE()マクロ自体を変更する事が出来なかったかと言うと、インラインアセンブラに__attribute__((noreturn))属性が指定出来ないからです。
//   もし、DIE()マクロをインラインアセンブラに変更してしまうと、__attribute__((noreturn))属性が付けられず、CコンパイラがDIE()の後も処理が継続すると見なしてしまい、生成されるコードが大幅に変わってしまいます。
//   Cコンパイラがdie2()の__attribute__((noreturn))を参照しての最適化が出来なくなり、却ってコードサイズが増える可能性も有ります。
//   上記の問題を防ぐために、今回のDIE()の変更は、dpcc33.exeで行うようにしました。
//   dpcc33.exeが変換するコードパターンは、念のために、「xld.w %r12,～;xld.w %r13,～;xcall die2」だけでなく、それに類似するパターン('x'無しや、最初の二行の逆転等)に対応するようにしてあります。
//   とは言え実際には、Cコンパイラは常に「xld.w %r12,～;xld.w %r13,～;xcall die2」のパターンを生成するようです。
//   尚、万一、それ以外のコードパターンが生成されて、dpcc33.exeが変換漏れしてdie2へのコールが残ってしまったとしても、動作上の問題は有りません。
//   今回の変換は、あくまでもコードサイズ節約のためだけであり、die2へのコールも、引き続き問題無く動作します。
//   ■■■■■重要(ここまで)■■■■■重要(ここまで)■■■■■重要(ここまで)■■■■■重要(ここまで)■■■■■重要(ここまで)■■■■■重要(ここまで)■■■■■重要(ここまで)■■■■■重要(ここまで)■■■■■
//{{2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
/*static*/ void new_die2_isr();
//}}2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
/*static*/ void new_assert_isr();
asm("
		.code
		.align		1
//{{2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
new_die2_isr:
		ld.w		%r9, 0					;//%r9  := expr = 0
//}}2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
new_assert_isr:
		;//-----------------------------------------------------;//
		;//□new_assert_isrの動作内容は以下の通りです。
		;// - (%r9=!0)だった場合は、インラインパラメータの後の、戻り位置へ戻ります。
		;//   その場合、%r9,%psrも含めて、全てのレジスタを破壊しません。
		;//   ただし、assert()マクロの展開コード自身が%r9を使用しているので、assert()マクロのインラインアセンブラのワーク・レジスタとして、“%r9”を指定する必要が有ります。
		;// - (%r9==0)だった場合は、エラー停止して、呼び出し元へは戻りません。
		;//   エラー画面には、インラインパラメータで指定された、ファイル名の文字列と行番号を表示します。
		;//-----------------------------------------------------;//
		;//□呼び出し側のコード形式は以下の通りです。
		;// - assert()マクロを展開する位置のアドレスがワードアライメントされていた場合:
		;//		・
		;//		・
		;//		ld.w	%r9, %r??			;//(0+ 0),2	┐
		;//		int	0				;//(0+ 2),2	│
		;//	pInlineParam:					;//		├assert()マクロによって展開されます。
		;//		.word	ファイル名の文字列のアドレス	;//(0+ 4),4	│
		;//		.half	行番号(0～65535)		;//(0+ 8),2	┘
		;//		・					;//  =10	←戻り位置
		;//		・
		;// - assert()マクロを展開する位置のアドレスがワードアライメントされていなかった場合:
		;//		・
		;//		・
		;//		ld.w	%r9, %r??			;//(2+ 0),2	┐
		;//		int	0				;//(2+ 2),2	│
		;//	pInlineParam:					;//		├assert()マクロによって展開されます。
		;//		(暗黙のnop)				;//(2+ 4),2	│
		;//		.word	ファイル名の文字列のアドレス	;//(2+ 6),4	│
		;//		.half	行番号(0～65535)		;//(2+10),2	┘
		;//		・					;//  =12	←戻り位置
		;//		・
		;//-----------------------------------------------------;//
		;//%r9     := expr
		;//[%sp+0] := %psr
		;//[%sp+4] := pInlineParam
		cmp		%r9, 0					;//%psr(Z) := !expr
		xld.w		%r9, [%sp+4]				;//%r9     :=          pInlineParam
		jreq.d		new_assert_isr_DIE			;//if(!expr) { goto DIE }
		add		%r9,  2					;//%r9     :=          pInlineParam + 2		*delay*
		and		%r9, ~2					;//%r9     :=         (pInlineParam + 2) & ~2
		add		%r9, (4+2)				;//%r9     := retp = ((pInlineParam + 2) & ~2) + (4/*ファイル名の文字列のアドレス*/ + 2/*行番号(0～65535)*/)
		xld.w		[%sp+4], %r9				;//[%sp+4] := retp
		reti							;//%psr := [%sp]+, %pc := [%sp]+
new_assert_isr_DIE:
		and		%r9, ~2					;//%r9     :=         (pInlineParam + 2) & ~2
		ld.w		%r12, [%r9]+				;//%r12    := file
		ld.uh		%r13, [%r9]				;//%r13    := line
		xld.w		%r9, [%sp+0]				;//%r9     := 元の%psr	←───┐
		xjp.d		die2					;//die2(file, line)		├この二行は無くても動作しますが、エラー画面中もメニュー画面へ戻った後も割り込み禁止のままになって、USB通信が出来なくなる問題が生じます。その問題を避けるために、'int 0'が退避した%psrを復元しておく事にしました。'int 0'を行う前に既に割り込み禁止だった場合はやはり問題が生じますが、それは仕方が有りません。割り込みルーチン内からもassert()が呼び出される可能性が有るので、もしここで明示的に割り込み許可してしまうと、エラー画面中にまた割り込みが発生してエラー画面が正常に表示されない恐れが有るからです。
		ld.w		%psr, %r9				;//%psr    := 元の%psr	←───┘		*delay*
");
//{{2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
//一回呼ぶとフックし、もう一度呼ぶと元に戻します。
static void hook_unhook_die2() {
	static PCETPENT old_die2_isr = new_die2_isr;
	old_die2_isr = pceVectorSetTrap(TRAP_SOFTINT2, old_die2_isr);
}
//}}2016/12/02追加:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
//一回呼ぶとフックし、もう一度呼ぶと元に戻します。
static void hook_unhook_assert() {
	static PCETPENT old_assert_isr = new_assert_isr;
//{{2016/12/02変更:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
//	old_assert_isr = pceVectorSetTrap(TRAP_SOFTINT0, old_assert_isr);
//↓2016/12/02変更:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
	old_assert_isr = pceVectorSetTrap(TRAP_SOFTINT3, old_assert_isr);
//}}2016/12/02変更:die2()もソフトウェア例外(2番)を使用するように変更しました。assert()が使用するソフトウェア例外の番号を、0番⇒3番に変更しました。
}
//}}2016/11/17追加:assert()マクロの実装を変更しました。(変更したファイルは、clippce.cとinclude/assert.hです。)
