/*	
 *	clippce.c
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2009 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[]を使うように変更しました。
 */
#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という値を書き込み、あとで検査します。

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

/* S1C33 Family Cコンパイラパッケージでは、errnoはアプリケーション側で定義する必要があります。
 * [S5U1C33000C_J.pdf p.140]
 */
int errno;

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

//{{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();
	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();
	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()
 *	pceFileDelete()
 *	pceFileRename() //非公開API
 *	pceFileLoad()
 *   以上の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;

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; }

/* * 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);
}

/****************************************************************************
 *	高精度パッドセンシング
 ****************************************************************************/

// * 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%未満程度なので、これで充分だと思います。
// - タイマでポーリングを行う方法以外に、入力割り込みによる監視も検討しましたが、ダメでした。
//   入力割り込みで監視ができればもっと負荷を下げられるのですが、以下の理由で採用できません。
//   カーネルの使用するポート入力割り込み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()を使ってください。

static struct {
	unsigned long (*old_pcePadGetDirect)();	/* +0,4: */
	         unsigned char prev;		/* +4,1: 前回、pcePadGetDirect()に返した値 */
	volatile unsigned char next;		/* +5,1: 次回、pcePadGetDirect()に返す値 */
} precision_pad;

#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の値を返します。
	 *   pceTimerSetCallback()のコールバック関数として呼ばれたときは、戻り値はダミーです。
	 */
	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

#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

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);
}

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

// * 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;
				}
			}
		} else {
			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);
}

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

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

static int  app_init();
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に置換しました。

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

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()へ移動しました。

//{{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()バグ修正パッチを適用

	/* ヒープマネージャAPIの排他制御を設定します。 */
	hook_unhook_heapman(); /* 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;
	*--stack_info.usp = (int)app_exit;	/* app_init()が制御を返した時に呼ばれる関数 */
	*--stack_info.usp = (int)app_init;	/* エントリーポイント(この中からmainを呼ぶ) */
	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でボリューム最大*/);
}

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)();
	}

	/* 確実にストリーム再生を停止します。 */
	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階調表\示使用時のみインストールするようにしました。

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

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

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

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

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

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より*/
}

static int
app_init()
{
	//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()が有効です。
	 */

	/* 高速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);

	///* 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()用ヒープの初期化を取りやめました。

	/* アプリケーション定義のエントリポイントへ。 */
	return main();
}

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;
}

/*****************************************************************************
 *	下位レベル関数/ダミー関数 : 標準Ｃライブラリ
 *****************************************************************************/

/* 下位レベル関数：	標準ライブラリ内部から呼ばれるもの。
 *			アプリケーションから直接使うことはありません。
 * ダミー関数：		標準ライブラリのヘッダで宣言されているが、
 *			実装されていない（ライブラリ内に存在しない）。
 * 従って、これらの関数を 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();
}

/* * 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がヌル終端されていないことを許す仕様ならば、
 *   以下の実装では正しく動作しません。(最初に行っているstrlen()が、どこまで行くか予\測できないから。)
 *   strstr()の正式な仕様を確認する必要があります。
 */
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;
}

/* 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へ移動)

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

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

/*****************************************************************************
 *	標準Ｃライブラリの補助
 *****************************************************************************/

char*
strdup(const char* string)
{
	int len = strlen(string);
	char* p = malloc(len + 1/*終端のnulの分、忘れずに!*/);
	if(p != NULL) strcpy(p, string);
	return p;
}

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;
}

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

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)の二バイト目が判定できません。 */
}

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

int
iscsym(int c)
{
	return isalnum(c) || (c == '_');
}

int
iscsymf(int c)
{
	return isalpha(c) || (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;
}

/* * 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*/

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

/* * 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'; /* 終端 */
}

/****************************************************************************
 *	コアCPU制御 (s1c33cpu.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*
");

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

void
usleep(unsigned long usec)
{
	unsigned long st, ed;

	/* 注意!pceTimerGetPrecisionCount()の戻り値はリニアな値ではありません。
	 * 差分を求めるにはpceTimerAdjustPrecisionCount()を使う必要があります。
	 */
	st = pceTimerGetPrecisionCount();
	do {
		ed = pceTimerGetPrecisionCount();
	} while(pceTimerAdjustPrecisionCount(st, ed) < usec);
}

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

//{{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()からも参照されます。 */
const unsigned char render_font_size[3][2] = { {5,10}, {8,16}, {4,6} };

/* 縁取りフォント描画のパラメータテーブル。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;
	if(type < 0 || 2 < type) DIE(); /* 無効なフォントタイプ */
	/*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);
}

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

#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);
}

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

#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:空きブロックの先頭インデクス               */
	/*}}オフセット固定部分：asmブロックからオフセット決め打ちで参照します。             */
	int BLKS;			/* コールバック一回当りの展開サンプル数             */
	PCEWAVEINFO wi[BLKN];		/* WAVEヘッダ(バッファは実行時に確保)               */
	STREAMCALLBACK* callback;	/* サンプル展開コールバック                         */
	int param;			/* コールバックへの任意パラメータ                   */
	/* * コールバックを呼び出す前に、スタックを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;
} STREAMINFO;
static STREAMINFO stream_info;

//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()としました。

/* * 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
stream_play(int BLKS, STREAMCALLBACK* callback, int param, int fram4)
{
	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;

	/* スタック切り替えを行うならば、FRAM4領域退避用メモリを確保します。 */
	if(fram4) {
		si->fram4_save = malloc(FRAM4_SIZE);
		if(si->fram4_save == NULL) DIE();
	}

	/* 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;
	}

	/* 再生開始前に、全ブロックにデータを満たしておきます。 */
	si->bc = BLKN;
	stream_fill();

	/* サウンド割り込みルーチンをフックし、Wave再生を開始します。
	 * この二つ処理の間にサウンド割り込みがかかってはいけません。
	 */
DISABLE;
	si->old_isr = pceVectorSetTrap(TRAP_HDM1, stream_isr);
	pceWaveDataOut(CH_STREAM, &si->wi[0]);
ENABLE;
}

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再生を停止し、サウンド割り込みルーチンを元に戻します。
	 * この二つ処理の間にサウンド割り込みがかかってはいけません。
	 */
DISABLE;
	pceWaveAbort(CH_STREAM);
	pceVectorSetTrap(TRAP_HDM1, si->old_isr);
	si->old_isr = NULL;
ENABLE;

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

	/* FRAM4領域退避用メモリを開放します。 */
	free(si->fram4_save);	/* 確保していなくても free(NULL) は安全です */
}

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

#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()
//{
//	/* 何もしません。 */
//}
#else	/*{{2004/12/12～現在の最新コードブロックです。*/
/* * 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
");
#endif

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

	int retval;
	short* wbuff;

	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;
		if(si->fram4_save) {
			/* スタック切り替えあり */
			memcpy(si->fram4_save, FRAM4_START, FRAM4_SIZE);					/* FRAM4領域退避 */
			////*(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);			/* スタック切り替え、コールバック呼び出し */
			memcpy(FRAM4_START, si->fram4_save, FRAM4_SIZE);					/* FRAM4領域復元 */
		} else {
			/* スタック切り替えなし */
			retval = si->callback(wbuff, si->param);						/* コールバック呼び出し */
		}
		if(retval != 0) {
			/* 展開に失敗したら、無音にします。(演奏終了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);
	}
}

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

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

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;
}

/*****************************************************************************
 *	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);
}

/*****************************************************************************
 *	
 *****************************************************************************/

