/*	
 *	clipfibr.c
 *
 *	ファイバ関数(Win32 API互換)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2015 Naoyuki Sawa
 *
 *	* Sat May 16 13:07:41 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	- 関数仕様は、Win32 APIのファイバ関数群とほぼ同じです。
 *	  使用方法の詳細は、Win32 APIの説明を参照してください。
 *	- 当モジュールの機能は、既存のcliptask.cの関数を使用しても、ほぼ同じ事が実現出来ます。
 *	  ただし、もしアプリケーションの要件が、複雑な同期制御が不要で、単純なコルーチンの動作だけが必要な場合は、
 *	  cliptask.cよりも当モジュールの関数を使う方が、アプリケーションの構造を単純に出来るという利点が有ります。
 */
#include "clip.h"
/****************************************************************************
 *	
 ****************************************************************************/
typedef struct _ST_Fiber {
	void*		data;
	void*		sp;
    //	unsigned char	stack[stackSize];
} ST_Fiber;
static ST_Fiber* CurrentFiber;
/*--------------------------------------------------------------------------*/
/*static*/ void FiberEntry();
asm("
		.code
		.align		1
FiberEntry:
		xld.w		%r12, [CurrentFiber]		;//%r12 := fiber = CurrentFiber
		xld.w		%r12, [%r12+0]			;//%r12 := data  = fiber->data
		xld.w		%r9, [%sp+0]			;//%r9  := fiberProc = [%sp++]
		xadd		%sp, %sp, 4			;//
		call		%r9				;//fiberProc(data)
		xld.w		%r12, [CurrentFiber]		;//%r12 := fiber = CurrentFiber
		xjp		DeleteFiber			;//    DeleteFiber(CurrentFiber)	//'DeleteFiber(CurrentFiber)'経由で'exit(0)'を呼び出す。FiberProc()が処理を返すとスレッドが終了するという、Win32 APIの動作に倣った。
");
/*--------------------------------------------------------------------------*/
void* ConvertThreadToFiber(void* data) {
	ST_Fiber* fiber;
	fiber = malloc(sizeof(ST_Fiber));								//スレッドは既にスタックを持っているので、stack[]の確保は不要である。
	if(!fiber) { DIE(); }
	fiber->data = data;
    //	fiber->sp   = 最初にSwitchToFiber()が呼ばれるまで不定値で構わない。
	CurrentFiber = fiber;
	return fiber;
}
/*--------------------------------------------------------------------------*/
void* CreateFiber(size_t stackSize, void (*fiberProc)(void*), void* data) {
	ST_Fiber* fiber;
	void** stack;
	if(!stackSize) { stackSize = 0x1000; }	//既定のスタックサイズ(調整可)
	stackSize = (stackSize + 3) & ~3;
	fiber = malloc(sizeof(ST_Fiber) + stackSize);
	if(!fiber) { DIE(); }
	fiber->data = data;
	stack       = (void**)((unsigned char*)(fiber + 1) + stackSize);
	*--stack    = fiberProc;
	*--stack    = FiberEntry;
	fiber->sp   = stack - 4/*初回のSwitchToFiber()の「popn %r3」用ダミー*/;
	return fiber;
}
/*--------------------------------------------------------------------------*/
void DeleteFiber(void* fiber) {
    //{{2015/05/16修正
    //	free(fiber);
    //	if(fiber == CurrentFiber) { exit(0); }								//カレントファイバを削除するとスレッドが終了するという、Win32 APIの動作に倣った。
    //↑この処理順序は間違い。
    //↑CreateFiber()で作成したファイバがFiberProc()が処理を返すか,DeleteFiber(GetCurrentFiber())を呼び出して終了する時、free(fiber)で開放したスタック上でexit(0)の処理を実行してしまう。
    //↓この処理順序が正しい。
	if(fiber == CurrentFiber) { exit(0); }								//カレントファイバを削除するとスレッドが終了するという、Win32 APIの動作に倣った。
	free(fiber);
    //}}2015/05/16修正
}
/*--------------------------------------------------------------------------*/
void SwitchToFiber(void* fiber);
asm("
		.code
		.align		1
		.global		SwitchToFiber
SwitchToFiber:
		xld.w		%r4, [CurrentFiber]		;//%r4  := CurrentFiber
		pushn		%r3				;//%r0〜%r3を退避する。
		xld.w		[%r4+4], %sp			;//        CurrentFiber->sp = %sp	─「xld.w [%rb+n],%sp」は展開されたコードが%r9を破壊する事に注意せよ。
		;//↑↓この順序で処理すれば、(fiber==CurrentFiber)が指定されても正しく動作する。
		xld.w		%r9, [%r12+4]			;//%r9  := fiber->sp			┐
		ld.w		%sp, %r9			;//%sp  := fiber->sp			┴「xld.w %sp,[%rb+n]」はext33に無い。明示的に展開して書く必要が有る。
		popn		%r3				;//%r0〜%r3を復元する。
		xld.w		[CurrentFiber], %r12		;//CurrentFiber = fiber
		ret
");
/*--------------------------------------------------------------------------*/
void* GetCurrentFiber() {
	return CurrentFiber;
}
/*--------------------------------------------------------------------------*/
void* GetFiberData() {
	return CurrentFiber->data;
}
/****************************************************************************
 *	使用例
 ****************************************************************************/
#if 0
void *f1,*f2;
void proc2(void* data){
 int num=(int)data;//どちらでも可
 //int num=(int)GetFiberData();//どちらでも可
 printf("proc2 called\n");
 do{
  printf("num=%d",num);
  SwitchToFiber(f1);
 }while(--num);
 //この後、終了する。
}
void app_main(){
 f1=ConvertThreadToFiber(NULL);
 f2=CreateFiber(0,proc2,(void*)5);
 //c1⇒c2(5)⇒c1⇒c2(4)⇒c1⇒c2(3)⇒c1⇒c2(2)⇒c1⇒c2(1)⇒c1⇒c2(0)⇒終了
 for(;;){
  SwitchToFiber(f2);
  putchar('\n');
 }
}
#endif
