unit sys_signal;

{$mode ObjFPC}{$H+}

interface

uses
 Windows,
 sys_types,
 atomic,
 LFQueue,
 signal,
 ucontext,
 sys_context;

type
 p_ksiginfo_t=^ksiginfo_t;
 ksiginfo_t=packed record
  pNext:p_ksiginfo_t;
  //
  ksi_info:siginfo_t;
 end;

 ksiginfo_list=object
  pHead,pTail:p_ksiginfo_t;
  procedure Insert(Node:p_ksiginfo_t);
  procedure Remove(pPrev,node:p_ksiginfo_t);
 end;

 p_sigqueue_t=^sigqueue_t;
 sigqueue_t=packed record
  _mask:sigset_t;

  _lock:Integer; //signal blocking
  _flag:DWORD;   //signal flags

  _queue_lock:DWORD;
  _event_lock:DWORD; //context send lock
  _event:DWORD;      //context event

  _signals:sigset_t;

  _queue:ksiginfo_list;

  _contexts:TIntrusiveMPSCQueue;
 end;

 p_ksig_context=^ksig_context;
 ksig_context=packed record
  next:p_ksig_context;
  ectx:TCONTEXT_EXTENDED;
  _rsp:QWORD;
 end;

Const
 SL_ALERTABLE=1;
 SL_NOINTRRUP=2;

Procedure sigqueue_init(sq:p_sigqueue_t);

function  _sig_pending(sq:p_sigqueue_t):DWORD; inline;
function  _sig_is_pending(sq:p_sigqueue_t):Boolean; inline;

function  _sigaddset(_set:p_sigset_t;signum:Integer):Integer;
function  _sigdelset(_set:p_sigset_t;signum:Integer):Integer;
function  _sigismember(_set:p_sigset_t;signum:Integer):Integer;
function  __sigprocmask(how:Integer;_set,oldset:p_sigset_t):Integer;

function  __sigaction(signum:Integer;act,oldact:p_sigaction_t):Integer;

procedure _sig_lock(flags:integer=0);
procedure __sig_lock(pt:Pointer;flags:integer=0);
procedure _sig_unlock(flags:integer=0);
procedure __sig_unlock(pt:Pointer;flags:integer=0);

function  _pthread_kill(t:Pointer;sig:Integer):Integer;

procedure _copy_ctx_from_sys(src:PCONTEXT;dst:p_ucontext_t);
procedure _copy_ctx_to_sys(src:p_ucontext_t;dst:PCONTEXT);

implementation

uses
 ntapi,
 spinlock,
 sys_kernel,
 sys_pthread;

Var
 ps_sigact:array[0..31] of sigaction_t;

 pf_deadlock:QWORD;

procedure ksiginfo_list.Insert(Node:p_ksiginfo_t);
begin
 if (pTail=nil) then
 begin
  pHead:=node;
  node^.pNext:=nil;
 end else
 begin
  pTail^.pNext:=node;
 end;
 pTail:=node;
end;

procedure ksiginfo_list.Remove(pPrev,node:p_ksiginfo_t);
begin
 if (pPrev=nil) then
 begin
  if (pHead=node) then
  begin
   pHead:=node^.pNext;
  end;
 end else
 begin
  pPrev^.pNext:=node^.pNext;
 end;
 if (node^.pNext=nil) then
 begin
  if (pTail=node) then
  begin
   pTail:=pPrev;
  end;
 end;
end;

Procedure sigqueue_init(sq:p_sigqueue_t);
var
 i:Byte;
begin
 if (sq=nil) then Exit;
 sq^:=Default(sigqueue_t);
 sq^._contexts.Create;
end;

function sigqueue_add(sq:p_sigqueue_t;si:p_siginfo_t):Integer;
var
 node:p_ksiginfo_t;
begin
 Result:=0;
 if (sq=nil) or (si=nil) then Exit;

 if (si^.si_signo=SIGKILL) or
    (si^.si_signo=SIGSTOP) then
 begin
  _sigaddset(@sq^._signals,si^.si_signo);
  Exit;
 end;

 node:=AllocMem(SizeOf(ksiginfo_t));
 if (node=nil) then
 begin
  _sigaddset(@sq^._signals,si^.si_signo);
  Exit(EAGAIN);
 end;

 node^.ksi_info:=si^;

 spin_lock(sq^._queue_lock);

   sq^._queue.Insert(node);
   _sigaddset(@sq^._signals,si^.si_signo);

 spin_unlock(sq^._queue_lock);

end;

function sigqueue_get(sq:p_sigqueue_t;signo:Integer;si:p_siginfo_t):Integer;
var
 node:p_ksiginfo_t;
 prev:p_ksiginfo_t;
 item:p_ksiginfo_t;
 count:DWORD;
begin
 if (sq=nil) or (si=nil) then Exit(0);

 if (_sigismember(@sq^._signals,signo)<>1) then Exit(0);

 count:=0;
 node:=nil;
 prev:=nil;

 spin_lock(sq^._queue_lock);

   item:=sq^._queue.pHead;
   While (item<>nil) do
   begin
    if (item^.ksi_info.si_signo=signo) then
    begin
     if (node=nil) then
     begin
      node:=item;
      sq^._queue.Remove(prev,item);
     end else
     begin
      Inc(count);
     end;
    end;

    prev:=item;
    item:=item^.pNext;
   end;

   if (node=nil) or (count=0) then
   begin
    _sigdelset(@sq^._signals,signo);
   end;

 spin_unlock(sq^._queue_lock);

 if (node=nil) then Exit(0);

 si^:=node^.ksi_info;
 FreeMem(node);

 Result:=signo;
end;

function sigqueue_push_ctx(sq:p_sigqueue_t;ectx:PCONTEXT_EXTENDED;_rsp:QWORD):Integer;
var
 node:p_ksig_context;
begin
 Result:=0;
 if (sq=nil) or (ectx=nil) then Exit;

 node:=AllocMem(SizeOf(ksig_context));
 if (node=nil) then Exit(EAGAIN);

 node^._rsp:=_rsp;

 if not CopyContextExtended(ectx,@node^.ectx) then
 begin
  FreeMem(node);
  Exit(EINVAL);
 end;

 sq^._contexts.Push(node);
end;

function sigqueue_pop_ctx(sq:p_sigqueue_t;ectx:PCONTEXT_EXTENDED;_rsp:QWORD):Integer;
var
 node:p_ksig_context;
begin
 Result:=0;
 if (sq=nil) or (ectx=nil) then Exit;

 node:=nil;
 sq^._contexts.Pop(node);
 if (node=nil) then Exit(EAGAIN);

 if (node^._rsp<>_rsp) then
 begin
  Result:=EFAULT;
 end;

 if not CopyContextExtended(@node^.ectx,ectx) then
 begin
  Result:=EINVAL;
 end;
 FreeMem(node);
end;

function _sig_pending(sq:p_sigqueue_t):DWORD; inline;
begin
 Result:=( load_acq_rel(sq^._signals.bits[0]) and (not load_acq_rel(sq^._mask.bits[0])) );
end;

function _sig_is_pending(sq:p_sigqueue_t):Boolean; inline;
begin
 Result:=_sig_pending(sq)<>0;
end;

function _sigaddset(_set:p_sigset_t;signum:Integer):Integer;
begin
 if (_set=nil) or
    (not _SIG_VALID(signum)) then Exit(EINVAL);
 signum:=signum-1;
 fetch_or(_set^.qwords[signum shr 6],1 shl (signum and 63));
 Result:=0;
end;

function _sigdelset(_set:p_sigset_t;signum:Integer):Integer;
begin
 if (_set=nil) or
    (not _SIG_VALID(signum)) then Exit(EINVAL);
 signum:=signum-1;
 fetch_and(_set^.qwords[signum shr 6],not(1 shl (signum and 63)));
 Result:=0;
end;

function _sigismember(_set:p_sigset_t;signum:Integer):Integer;
begin
 if (_set=nil) or
    (not _SIG_VALID(signum)) then Exit(EINVAL);
 signum:=signum-1;
 Result:=Integer((_set^.qwords[signum shr 6] and (1 shl (signum and 63)))<>0);
end;

function __sigprocmask(how:Integer;_set,oldset:p_sigset_t):Integer;
var
 t:pthread;
begin
 t:=tcb_thread;
 if (t=nil) then Exit(EINVAL);

 if (_set=nil) then
 begin
  if (oldset<>nil) then oldset^:=t^.sig._mask;
  Exit(0);
 end;

 Case how of
  SIG_BLOCK:
    begin
     if (oldset<>nil) then oldset^:=t^.sig._mask;
     t^.sig._mask.qwords[0]:=t^.sig._mask.qwords[0] or _set^.qwords[0];
     t^.sig._mask.qwords[1]:=t^.sig._mask.qwords[1] or _set^.qwords[1];
    end;
  SIG_UNBLOCK:
    begin
     if (oldset<>nil) then oldset^:=t^.sig._mask;
     t^.sig._mask.qwords[0]:=t^.sig._mask.qwords[0] and (not _set^.qwords[0]);
     t^.sig._mask.qwords[1]:=t^.sig._mask.qwords[1] and (not _set^.qwords[1]);
    end;
  SIG_SETMASK:
    begin
     if (oldset<>nil) then oldset^:=t^.sig._mask;
     t^.sig._mask:=_set^;
    end;
  else
    Exit(EINVAL);
 end;

 Result:=0;
end;

function __sigaction(signum:Integer;act,oldact:p_sigaction_t):Integer;
var
 idx:DWORD;
 tmp:sigaction_t;
begin
 if not _SIG_VALID_32(signum) then Exit(EINVAL);

 Writeln('sigaction:',_get_sig_str(signum));

 if (act=nil) then
 begin
  tmp:=Default(sigaction_t);
 end else
 begin
  tmp:=act^;
 end;

 Case signum of
  SIGKILL,
  SIGSTOP:
    begin
     case tmp.u.code of
      SIG_DFL:;
      else
       Exit(EINVAL);
     end;
    end;
  else;
 end;

 idx:=_SIG_IDX(signum);

 if (oldact<>nil) then
 begin
  oldact^:=ps_sigact[idx];
 end;

 ps_sigact[idx]:=tmp;

 Result:=0;
end;

function _sig_dfl(sig:Integer):Integer;
begin
 Case sig of
  SIGURG  ,
  SIGCHLD ,
  SIGWINCH,
  SIGINFO :Result:=SIG_IGN;
  else     Result:=SIG_ERR;
 end;
end;

function _sig_act_type(sig:Integer;var act:sigaction_t):Integer;
begin
 Result:=0;
 Case act.u.code  of
  SIG_DFL:Case _sig_dfl(sig) of
           SIG_IGN:Result:=SIG_IGN;
           SIG_ERR:Result:=SIG_ERR;
          end;
  SIG_IGN  :Result:=SIG_IGN;
  SIG_ERR  :Result:=SIG_ERR;
  SIG_CATCH:Result:=SIG_ERR;
  SIG_HOLD :Result:=SIG_ERR;
 end;
end;

const
 ALERTABLE_FLAG=1;
 SPIN_WAIT_FLAG=2;

function __sig_self_interrupt(t:pthread):Integer; forward;

procedure _sig_lock(flags:integer=0);
begin
 __sig_lock(tcb_thread,flags);
end;

procedure __sig_lock(pt:Pointer;flags:integer=0);
label
 tryagain;
var
 t:pthread;
 i:Integer;
 pc:QWORD;
begin
 if (pt=nil) then Exit;
 t:=pt;

 if ((flags and SL_ALERTABLE)<>0) then
 begin
  fetch_or(t^.sig._flag,ALERTABLE_FLAG);
 end;

 i:=fetch_add(t^.sig._lock,1);

 //need to interrupt
 if ((flags and SL_NOINTRRUP)=0) and ((i=0) or ((flags and SL_ALERTABLE)<>0)) then
 begin
  tryagain:

  fetch_or(t^.sig._flag,SPIN_WAIT_FLAG);

   pc:=0;
   While (_sig_is_pending(@t^.sig)) do
   begin
    spin_pause;
    Inc(pc);
    if (pc>pf_deadlock) then
    begin
     //deadlock
     __sig_self_interrupt(t);
    end;
   end;

  fetch_and(t^.sig._flag,DWORD(not SPIN_WAIT_FLAG));

  if (_sig_is_pending(@t^.sig)) then goto tryagain;

 end;

end;

procedure _sig_unlock(flags:integer=0);
begin
 __sig_unlock(tcb_thread,flags);
end;

procedure __sig_unlock(pt:Pointer;flags:integer=0);
label
 tryagain;
var
 t:pthread;
 i:Integer;
 Alertable:Boolean;
 pc:QWORD;
begin
 if (pt=nil) then Exit;
 t:=pt;

 i:=load_acq_rel(t^.sig._lock);
 Alertable:=((t^.sig._flag and ALERTABLE_FLAG)<>0);

 i:=fetch_sub(t^.sig._lock,1);

 //need to interrupt
 if ((flags and SL_NOINTRRUP)=0) and ((i=1) or Alertable) then
 begin
  tryagain:

  fetch_or(t^.sig._flag,SPIN_WAIT_FLAG);

   pc:=0;
   While (_sig_is_pending(@t^.sig)) do
   begin
    spin_pause;
    Inc(pc);
    if (pc>pf_deadlock) then
    begin
     //deadlock
     __sig_self_interrupt(t);
    end;
   end;

  fetch_and(t^.sig._flag,DWORD(not (ALERTABLE_FLAG or SPIN_WAIT_FLAG)));

  if (_sig_is_pending(@t^.sig)) then goto tryagain;

 end;

 if ((flags and SL_NOINTRRUP)=0) then
 begin
  fetch_and(t^.sig._flag,DWORD(not ALERTABLE_FLAG));
 end;
end;

//var
// _test_counter:DWORD;

procedure __sig_test(t:pthread;ectx:PCONTEXT_EXTENDED); MS_ABI_Default;
var
 signo:Integer;
 sact:sigaction_t;
 info:siginfo_t;
 ucontext:ucontext_t;
 old:sigset_t;
 errno:QWORD;
 //_rsp:QWORD;
 _rbp:QWORD;
 sigpending:DWORD;
 win_err:DWORD;
 _lock:Integer;
 setmask:Boolean;
begin
 if (t=nil) then Exit;

 win_err:=GetLastError;
 errno:=t^.errno;

 While true do
 begin

  sigpending:=_sig_pending(@t^.sig);
  if (sigpending=0) then Break;

  signo:=BsfDWord(sigpending)+1;

  While (sigqueue_get(@t^.sig,signo,@info)<>0) do
  begin

   //Writeln('>__sig_test:'{,system.InterlockedIncrement(_test_counter)},':',t^.ThreadId);

   sact:=ps_sigact[_SIG_IDX(signo)];

   if ((sact.sa_flags and SA_RESETHAND)<>0) then
   begin
    ps_sigact[_SIG_IDX(signo)]:=Default(sigaction_t);
   end;

   Case _sig_act_type(info.si_signo,sact) of
    SIG_IGN:;
    SIG_ERR:;
    else
     begin
      //errno:=t^.errno;
      setmask:=((sact.sa_flags and SA_NODEFER)=0);

      if setmask then
      begin
       old:=t^.sig._mask;
       t^.sig._mask:=sact.sa_mask;
      end;

      ucontext:=Default(ucontext_t);
      _copy_ctx_from_sys(ectx^.CONTEXT,@ucontext);
      ucontext.uc_sigmask:=t^.sig._mask;
      ucontext.uc_mcontext.mc_err:=errno;

      //asm
      // //Mov %rsp,_rsp
      // Mov %rbp,_rbp
      //end;

      //link break from???
      //ucontext.uc_mcontext.mc_lbrfrom :=_rsp;
      //link break to???
      //ucontext.uc_mcontext.mc_lbrto   :=_rbp; //bottom in GC_push_all_stack

      //ucontext.uc_mcontext.mc_lbrfrom :=ucontext.uc_mcontext.mc_rsp;
      //ucontext.uc_mcontext.mc_lbrto   :=ucontext.uc_mcontext.mc_rsp;

      _lock:=t^.sig._lock;

      if ((sact.sa_flags and SA_SIGINFO)=0) then
      begin
       //sa_handler
       sact.u.sa_handler(info.si_signo,info.si_code,@ucontext);
      end else
      begin
       //sa_sigaction
       sact.u.sa_sigaction(info.si_signo,@info,@ucontext);
      end;

      if (_lock<>t^.sig._lock) then
      begin
       Assert(false);
      end;

      if setmask then
      begin
       t^.sig._mask:=old;
      end;
      //t^.errno:=errno;

     end;
   end;

   //Writeln('<__sig_test:'{,_test_counter,':'},t^.ThreadId);

  end;

 end;

 t^.errno:=errno;
 SetLastError(win_err);
end;

//compiler i hate you
procedure __sig_test_align(t:pthread;ectx:PCONTEXT_EXTENDED); MS_ABI_Default; assembler; nostackframe;
asm
 pushq %rbp
 movq %rsp, %rbp
 andq $-32, %rsp //align stack by AVX
 call __sig_test
 lea  0x0(%rbp),%rsp
 popq %rbp
 ret
end;

procedure __sig_interrupt(t:pthread); MS_ABI_Default;
var
 ctx:TCONTEXT_EXTENDED;
 rsp:QWORD;
begin
 if (t=nil) then Exit;

 asm
  Movq %rbp,rsp
 end;

 if (sigqueue_pop_ctx(@t^.sig,@ctx,rsp+8)<>0) then
 begin
  asm
   ud2
  end;
 end;

 //Writeln('>__sig_interrupt:',t^.ThreadId,' ',t^.sig._lock);

 repeat
  __sig_test_align(t,@ctx);
 until event_try_disable(t^.sig._event);

 NtContinue(ctx.CONTEXT,False);
 asm
  ud2
 end;
end;

procedure _update_ctx(ctx:PCONTEXT); MS_ABI_Default; assembler; nostackframe;
asm
 movq %rbx,TCONTEXT.rbx(%rcx)
 movq %rbp,TCONTEXT.rbp(%rcx)
 movq %r12,TCONTEXT.r12(%rcx)
 movq %r13,TCONTEXT.r13(%rcx)
 movq %r14,TCONTEXT.r14(%rcx)
 movq %r15,TCONTEXT.r15(%rcx)
 movq %rsi,TCONTEXT.rsi(%rcx)
 movq %rdi,TCONTEXT.rdi(%rcx)
 leaq 8(%rsp),%rax
 movq %rax,TCONTEXT.rsp(%rcx)
end;

function __sig_self_interrupt(t:pthread):Integer;
var
 ctx:TCONTEXT_EXTENDED;
 rsp:QWORD;
label
 eoi;
begin

 event_try_enable(t^.sig._event); //mark change

 //Writeln('__sig_self_interrupt');

 if not InitializeContextExtended(@ctx) then Exit(ESRCH);
 if (NtGetContextThread(t^.handle,ctx.CONTEXT)<>STATUS_SUCCESS) then Exit(ESRCH);

 _update_ctx(ctx.CONTEXT);
 ctx.CONTEXT^.Rip:=qword(@eoi);

 rsp:=AlignDw(ctx.CONTEXT^.Rsp-SizeOf(Pointer),SizeOf(Pointer));

 Result:=sigqueue_push_ctx(@t^.sig,@ctx,rsp);
 if (Result<>0) then Exit;

 ctx.CONTEXT^.Rip:=qword(@__sig_interrupt);
 ctx.CONTEXT^.Rcx:=qword(t);
 ctx.CONTEXT^.Rsp:=rsp;

 //Writeln('beg Sptr=',HexStr(Sptr));

 //Writeln('>NtContinue:',HexStr(ctx.CONTEXT^.Rip,16));
 NtContinue(ctx.CONTEXT,False);

 eoi:

 //Writeln('end Sptr=',HexStr(Sptr));

 Result:=0;
end;

procedure _apc_null(dwParam:PTRUINT); stdcall;
begin
end;

//var
// _send_counter:DWORD;

function nt_send_interrupt(t:pthread;signo:Integer):Integer;
label
 tryagain;
var
 ctx:TCONTEXT_EXTENDED;
 rsp:QWORD;
 w:LARGE_INTEGER;
begin
 Result:=0;
 tryagain:

 if (NtSuspendThread(t^.handle,nil)<>STATUS_SUCCESS) then
 begin
  Exit(ESRCH);
 end;

 w.QuadPart:=0;
 if (NtWaitForSingleObject(t^.handle,False,@w)<>STATUS_TIMEOUT) then
 begin
  NtResumeThread(t^.handle,nil);
  Exit(ESRCH);
 end;

 if (_sigismember(@t^.sig._signals,signo)<>1) then
 begin
  //signal is gone
  NtResumeThread(t^.handle,nil);
  Exit(0);
 end;

 if (t^.sig._lock=0) or ((t^.sig._flag and SPIN_WAIT_FLAG)<>0) then //(not locked) or wait
 begin

  if not InitializeContextExtended(@ctx) then
  begin
   NtResumeThread(t^.handle,nil);
   Exit(ESRCH);
  end;

  if (NtGetContextThread(t^.handle,ctx.CONTEXT)<>STATUS_SUCCESS) then
  begin
   NtResumeThread(t^.handle,nil);
   Exit(ESRCH);
  end;

  if IS_SYSCALL(ctx.CONTEXT^.Rip) then //system call in code without blocking
  begin
   //skip
   //Writeln('Warn syscall:0x',HexStr(ctx.CONTEXT^.Rax,4));

   //store_release(t^.sig._wait,1);

   if ((ps_sigact[_SIG_IDX(signo)].sa_flags and SA_RESTART)<>0) then
   begin
    NtResumeThread(t^.handle,nil);
    w.QuadPart:=-10000;
    SwDelayExecution(False,@w); //100ms
    goto tryagain;
   end else
   begin
    NtResumeThread(t^.handle,nil);
    Exit(EAGAIN);
   end;

  end else
  begin

   rsp:=AlignDw(ctx.CONTEXT^.Rsp-SizeOf(Pointer),SizeOf(Pointer));

   Result:=sigqueue_push_ctx(@t^.sig,@ctx,rsp);
   if (Result<>0) then
   begin
    NtResumeThread(t^.handle,nil);
    Exit;
   end;

   ctx.CONTEXT^.Rip:=qword(@__sig_interrupt);
   ctx.CONTEXT^.Rcx:=qword(t);
   ctx.CONTEXT^.Rsp:=rsp;

   if (NtSetContextThread(t^.handle,ctx.CONTEXT)<>STATUS_SUCCESS) then
   begin
    NtResumeThread(t^.handle,nil);
    Exit(ESRCH);
   end;

  end;

  NtResumeThread(t^.handle,nil);
 end else
 if ((t^.sig._flag and ALERTABLE_FLAG)<>0) then //Alertable
 begin
  NtQueueApcThread(t^.handle,@_apc_null,nil,nil,0);

  NtResumeThread(t^.handle,nil);

  if ((ps_sigact[_SIG_IDX(signo)].sa_flags and SA_RESTART)<>0) then
  begin
   w.QuadPart:=-10000;
   SwDelayExecution(False,@w); //100ms
   goto tryagain;
  end else
  begin
   Exit(EAGAIN);
  end;

 end else
 begin
  //locked

  NtResumeThread(t^.handle,nil);

  if ((ps_sigact[_SIG_IDX(signo)].sa_flags and SA_RESTART)<>0) then
  begin
   w.QuadPart:=-10000;
   SwDelayExecution(False,@w); //100ms
   goto tryagain;
  end else
  begin
   Exit(EAGAIN);
  end;

 end;
end;

function __sig_send_interrupt(t:pthread;signo:Integer):Integer;
begin
 Result:=0;

 Writeln('__sig_send_interrupt:'{,system.InterlockedIncrement(_send_counter),':'},t^.ThreadId);

 event_try_enable(t^.sig._event); //mark change

 if spin_trylock(t^.sig._event_lock) then
 begin
  Result:=nt_send_interrupt(t,signo);
  spin_unlock(t^.sig._event_lock);
 end;

end;

function _pthread_kill(t:Pointer;sig:Integer):Integer;
var
 idx:DWORD;
 act:sigaction_t;
 sinfo:siginfo_t;
begin
 if (t=nil) then Exit(EINVAL);

 Writeln('_pthread_kill:',sig,':',pthread(t)^.ThreadId);

 if (sig=0) then Exit(0); //check pthread

 if not _SIG_VALID_32(sig) then Exit(EINVAL);

 idx:=_SIG_IDX(sig);

 act:=ps_sigact[idx];

 Case _sig_act_type(sig,act) of
  SIG_IGN:
   begin
    Exit(0);
   end;
  SIG_ERR:
    begin
     DebugBreak;
     md_halt(0);
    end;
  else;
 end;

 sinfo:=Default(siginfo_t);
 sinfo.si_signo:=sig;
 sinfo.si_code :=SI_USER;
 sinfo.si_pid  :=GetCurrentProcessId;

 Result:=sigqueue_add(@pthread(t)^.sig,@sinfo);
 if (Result<>0) then Exit;

 if (t=tcb_thread) then
 begin
  _sig_lock;
  Result:=__sig_self_interrupt(t);
  _sig_unlock;
 end else
 begin
  _sig_lock;
  Result:=__sig_send_interrupt(t,sig);
  _sig_unlock;
 end;
end;

procedure _copy_ctx_from_sys(src:PCONTEXT;dst:p_ucontext_t);
var
 flags:DWORD;
 context_ex:PCONTEXT_EX;
 xs:PXSTATE;
 uc_xsave:PXmmSaveArea;
 uc_xstate:PXSTATE;
begin
 if (src=nil) or (dst=nil) then Exit;

 flags:=src^.ContextFlags and (not CONTEXT_AMD64);

 context_ex:=PCONTEXT_EX(src+1);
 xs:=PXSTATE(PByte(context_ex)+context_ex^.XState.Offset);

 uc_xsave:=PXmmSaveArea(@dst^.uc_mcontext.mc_fpstate);
 uc_xstate:=PXSTATE(uc_xsave+1);

 if ((flags and CONTEXT_INTEGER)<>0) then
 begin
  dst^.uc_flags:=dst^.uc_flags or _UC_CPU;

  dst^.uc_mcontext.mc_rax:=src^.Rax;
  dst^.uc_mcontext.mc_rbx:=src^.Rbx;
  dst^.uc_mcontext.mc_rcx:=src^.Rcx;
  dst^.uc_mcontext.mc_rdx:=src^.Rdx;
  dst^.uc_mcontext.mc_rsi:=src^.Rsi;
  dst^.uc_mcontext.mc_rdi:=src^.Rdi;
  dst^.uc_mcontext.mc_r8 :=src^.R8;
  dst^.uc_mcontext.mc_r9 :=src^.R9;
  dst^.uc_mcontext.mc_r10:=src^.R10;
  dst^.uc_mcontext.mc_r11:=src^.R11;
  dst^.uc_mcontext.mc_r12:=src^.R12;
  dst^.uc_mcontext.mc_r13:=src^.R13;
  dst^.uc_mcontext.mc_r14:=src^.R14;
  dst^.uc_mcontext.mc_r15:=src^.R15;
 end;

 if ((flags and CONTEXT_CONTROL)<>0) then
 begin
  dst^.uc_flags:=dst^.uc_flags or _UC_CPU;

  dst^.uc_mcontext.mc_rsp   :=src^.Rsp;
  dst^.uc_mcontext.mc_rbp   :=src^.Rbp;
  dst^.uc_mcontext.mc_rip   :=src^.Rip;
  dst^.uc_mcontext.mc_rflags:=src^.EFlags;
  dst^.uc_mcontext.mc_cs    :=src^.Segcs;
  dst^.uc_mcontext.mc_ss    :=src^.Segss;
 end;

 if ((flags and CONTEXT_SEGMENTS)<>0) then
 begin
  dst^.uc_flags:=dst^.uc_flags or _UC_CPU;

  dst^.uc_mcontext.mc_ds:=src^.SegDs;
  dst^.uc_mcontext.mc_es:=src^.SegEs;
  dst^.uc_mcontext.mc_fs:=src^.SegFs;
  dst^.uc_mcontext.mc_gs:=src^.SegGs;
 end;

 if ((flags and CONTEXT_FLOATING_POINT)<>0) then
 begin
  dst^.uc_flags:=dst^.uc_flags or _UC_FPU;

  uc_xsave^:=src^.FltSave;
  uc_xstate^.Mask:=uc_xstate^.Mask or XSTATE_MASK_LEGACY;
 end;

 if ((flags and CONTEXT_XSTATE)<>0) then
 begin
  if ((xs^.Mask and XSTATE_MASK_AVX)<>0) then
  begin
   uc_xstate^.Mask:=uc_xstate^.Mask or XSTATE_MASK_AVX;
   Move(xs^.YmmContext,uc_xstate^.YmmContext,SizeOf(TYMMCONTEXT));
  end;
 end;

 if ((flags and CONTEXT_DEBUG_REGISTERS)<>0) then
 begin
  //save it somewhere
  dst^.uc_mcontext.mc_spare[0]:=src^.Dr0;
  dst^.uc_mcontext.mc_spare[1]:=src^.Dr1;
  dst^.uc_mcontext.mc_spare[2]:=src^.Dr2;
  dst^.uc_mcontext.mc_spare[3]:=src^.Dr3;
  dst^.uc_mcontext.mc_spare[4]:=src^.Dr6;
  dst^.uc_mcontext.mc_spare[5]:=src^.Dr7;
 end;

 //fix me
 dst^.uc_mcontext.mc_lbrfrom:=src^.Rsp; //LastBranchFromRip
 dst^.uc_mcontext.mc_lbrto  :=src^.Rbp; //LastBranchToRip

 dst^.uc_flags:=dst^.uc_flags or (flags shl 8); //set as extended

 dst^.uc_mcontext.mc_addr    :=dst^.uc_mcontext.mc_rip;
 dst^.uc_mcontext.mc_len     :=SizeOf(mcontext_t);
 dst^.uc_mcontext.mc_flags   :=_MC_HASSEGS;
 dst^.uc_mcontext.mc_fpformat:=_MC_FPFMT_XMM;
 dst^.uc_mcontext.mc_ownedfp :=_MC_FPOWNED_FPU;
end;

procedure _copy_ctx_to_sys(src:p_ucontext_t;dst:PCONTEXT);
var
 flags:DWORD;
 context_ex:PCONTEXT_EX;
 xs:PXSTATE;
 uc_xsave:PXmmSaveArea;
 uc_xstate:PXSTATE;
begin
 if (src=nil) or (dst=nil) then Exit;

 context_ex:=PCONTEXT_EX(dst+1);
 xs:=PXSTATE(PByte(context_ex)+context_ex^.XState.Offset);

 uc_xsave:=PXmmSaveArea(@src^.uc_mcontext.mc_fpstate);
 uc_xstate:=PXSTATE(uc_xsave+1);

 flags:=(src^.uc_flags shr 8); //get extended

 if (flags=0) then //nop, get by other
 begin
  if ((src^.uc_flags and _UC_CPU)<>0) then
  begin
   flags:=flags or CONTEXT_INTEGER or CONTEXT_CONTROL or CONTEXT_SEGMENTS;
  end;
  if ((src^.uc_flags and _UC_FPU)<>0) then
  begin
   flags:=flags or CONTEXT_FLOATING_POINT;
  end;
  if ((uc_xstate^.Mask and XSTATE_MASK_AVX)<>0) then
  begin
   flags:=flags or CONTEXT_XSTATE;
  end;
  flags:=flags and (not CONTEXT_AMD64);
 end;

 flags:=flags and dst^.ContextFlags; //filter
 dst^.ContextFlags:=flags or CONTEXT_AMD64; //update

 if ((flags and CONTEXT_INTEGER)<>0) then
 begin
  dst^.Rax:=src^.uc_mcontext.mc_rax;
  dst^.Rbx:=src^.uc_mcontext.mc_rbx;
  dst^.Rcx:=src^.uc_mcontext.mc_rcx;
  dst^.Rdx:=src^.uc_mcontext.mc_rdx;
  dst^.Rsi:=src^.uc_mcontext.mc_rsi;
  dst^.Rdi:=src^.uc_mcontext.mc_rdi;
  dst^.R8 :=src^.uc_mcontext.mc_r8;
  dst^.R9 :=src^.uc_mcontext.mc_r9;
  dst^.R10:=src^.uc_mcontext.mc_r10;
  dst^.R11:=src^.uc_mcontext.mc_r11;
  dst^.R12:=src^.uc_mcontext.mc_r12;
  dst^.R13:=src^.uc_mcontext.mc_r13;
  dst^.R14:=src^.uc_mcontext.mc_r14;
  dst^.R15:=src^.uc_mcontext.mc_r15;
 end;

 if ((flags and CONTEXT_CONTROL)<>0) then
 begin
  dst^.Rsp   :=src^.uc_mcontext.mc_rsp;
  dst^.Rbp   :=src^.uc_mcontext.mc_rbp;
  dst^.Rip   :=src^.uc_mcontext.mc_rip;
  dst^.EFlags:=src^.uc_mcontext.mc_rflags;
  dst^.Segcs :=src^.uc_mcontext.mc_cs;
  dst^.Segss :=src^.uc_mcontext.mc_ss;
 end;

 if ((flags and CONTEXT_SEGMENTS)<>0) then
 begin
  dst^.SegDs:=src^.uc_mcontext.mc_ds;
  dst^.SegEs:=src^.uc_mcontext.mc_es;
  dst^.SegFs:=src^.uc_mcontext.mc_fs;
  dst^.SegGs:=src^.uc_mcontext.mc_gs;
 end;

 if ((flags and CONTEXT_FLOATING_POINT)<>0) then
 begin

  if ((uc_xstate^.Mask and XSTATE_MASK_LEGACY_FLOATING_POINT)<>0) then
  begin
   Move(uc_xsave^,dst^.FltSave,ptruint(@PXmmSaveArea(nil)^.MxCsr));
   Move(uc_xsave^.FloatRegisters,dst^.FltSave.FloatRegisters,SizeOf(TXmmSaveArea.FloatRegisters));
  end else
  begin
   FillChar(dst^.FltSave,ptruint(@PXmmSaveArea(nil)^.MxCsr),0);
   FillChar(dst^.FltSave.FloatRegisters,SizeOf(TXmmSaveArea.FloatRegisters),0);
   dst^.FltSave.ControlWord:=$37f;
  end;

  if ((uc_xstate^.Mask and XSTATE_MASK_LEGACY_SSE)<>0) then
  begin
   Move(uc_xsave^.XmmRegisters,dst^.FltSave.XmmRegisters,SizeOf(TXmmSaveArea.XmmRegisters));
   dst^.FltSave.MxCsr     :=uc_xsave^.MxCsr;
   dst^.FltSave.MxCsr_Mask:=uc_xsave^.MxCsr_Mask;
  end else
  begin
   FillChar(dst^.FltSave.XmmRegisters,SizeOf(TXmmSaveArea.XmmRegisters),0);
   dst^.FltSave.MxCsr     :=$1f80;
   dst^.FltSave.MxCsr_Mask:=$2ffff;
  end;

  dst^.MxCsr:=dst^.FltSave.MxCsr;
 end;

 if ((flags and CONTEXT_XSTATE)<>0) then
 begin
  xs^.Mask:=uc_xstate^.Mask;
  if ((uc_xstate^.Mask and XSTATE_MASK_AVX)<>0) then
  begin
   Move(uc_xstate^.YmmContext,xs^.YmmContext,SizeOf(TYMMCONTEXT));
  end;
 end;

 if ((flags and CONTEXT_DEBUG_REGISTERS)<>0) then
 begin
  //save it somewhere
  dst^.Dr0:=src^.uc_mcontext.mc_spare[0];
  dst^.Dr1:=src^.uc_mcontext.mc_spare[1];
  dst^.Dr2:=src^.uc_mcontext.mc_spare[2];
  dst^.Dr3:=src^.uc_mcontext.mc_spare[3];
  dst^.Dr6:=src^.uc_mcontext.mc_spare[4];
  dst^.Dr7:=src^.uc_mcontext.mc_spare[5];
 end;

end;

Procedure Init;
var
 pc:QWORD;
begin
 pf_deadlock:=1;
 NtQueryPerformanceCounter(@pc,@pf_deadlock);
 pf_deadlock:=pf_deadlock*2;
end;

initialization
 Init;

end.

