unit kern_jit_dynamic;

{$mode ObjFPC}{$H+}
{$CALLING SysV_ABI_CDecl}

interface

uses
 mqueue,
 hamt,
 //g23tree,
 g_node_splay,
 murmurhash,
 x86_jit,
 kern_jit_ctx,
 kern_jit_asm,
 kern_rwlock,
 kern_thr,
 kern_stub;

{
  entry_point -> +----------+    +---------+
                 |code_chunk| -> |code_blob|
  entry_point -> |          |    |         |
                 +----------+    |         |
  entry_point -> +----------+    |         |
                 |code_chunk| -> |         |
  entry_point -> |          |    |         |
                 +----------+    +---------+
}

type
 p_jit_dynamic_blob=^t_jit_dynamic_blob;

 p_jit_entry_point=^t_jit_entry_point;
 t_jit_entry_point=object
  next:p_jit_entry_point;
  blob:p_jit_dynamic_blob;
  src :Pointer; //<-guest
  dst :Pointer; //<-host
  entry_public:Integer;
  procedure inc_ref(name:pchar);
  procedure dec_ref(name:pchar);
 end;

 p_jinstr_len=^t_jinstr_len;
 t_jinstr_len=packed record
  original:0..31;  //5
  LF_JMP  :0..1;
  bit6    :0..1;
  recompil:0..511; //9
 end;

 p_jcode_chunk=^t_jcode_chunk;
 t_jcode_chunk=object
  entry:TAILQ_ENTRY;    //entry_chunk_list

  next  :p_jcode_chunk; //t_jit_dynamic_blob.chunk_list
  //pLeft :p_jcode_chunk;
  //pRight:p_jcode_chunk;
  blob  :p_jit_dynamic_blob;
  tobj  :Pointer; //p_vm_track_object
  start :QWORD;   //<-guest
  __end :QWORD;   //<-guest
  dest  :QWORD;   //<-host
  d_end :QWORD;   //<-host
  hash  :QWORD;   //MurmurHash64A(Pointer(start),__end-start,$010CA1C0DE);
  count :QWORD;
  table :record end; //p_jinstr_len[]
  function  c(n1,n2:p_jcode_chunk):Integer; static;
  procedure inc_ref(name:pchar);
  procedure dec_ref(name:pchar);
  function  is_mark_del:Boolean;
  function  find_host_by_guest(addr:QWORD):QWORD;
  function  find_guest_by_host(addr:QWORD):QWORD;
  function  cross_guest(c_start,c___end:QWORD):Boolean;
  function  cross_host (c_start,c___end:QWORD):Boolean;
 end;

 //t_jcode_chunk_set=specialize T23treeSet<p_jcode_chunk,t_jcode_chunk>;

 p_jplt_cache=^t_jplt_cache;
 t_jplt_cache=object(t_jplt_cache_asm)
  //
  pLeft :p_jplt_cache; //jpltc_curr
  pRight:p_jplt_cache; //jpltc_curr
  //
  entry:TAILQ_ENTRY;   //jpltc_attc
  //
  self_block:Pointer;
  dest_block:Pointer;
  //
  function c(n1,n2:p_jplt_cache):Integer; static;
 end;

 t_jplt_cache_set=specialize TNodeSplay<t_jplt_cache>;

 t_jit_dynamic_blob=object
  var
   entry_list:p_jit_entry_point;
   chunk_list:p_jcode_chunk;

   jpltc_curr:t_jplt_cache_set;
   jpltc_attc:TAILQ_HEAD;

   mchunk:p_stub_chunk;

   base:Pointer;
   size:ptruint;

   plta:p_jit_plt;
   pltc:ptruint;

   plt_stub:t_jplt_cache;

   lock:Pointer;
   refs:Integer;

   attach_count:Integer;

  procedure inc_ref(name:pchar);
  procedure dec_ref(name:pchar);
  procedure inc_attach_count;
  function  dec_attach_count:Boolean;
  function  find_guest_by_host(addr:QWORD):QWORD;
  function  cross_host(c_start,c___end:QWORD):Boolean;
  procedure Free;
  function  add_entry_point(src,dst:Pointer):p_jit_entry_point;
  procedure free_entry_point(node:p_jit_entry_point);
  procedure init_plt;
  procedure attach_plt_cache(uplock:p_jit_dynamic_blob;node:p_jplt_cache);
  procedure detach_plt_cache(uplock:p_jit_dynamic_blob;node:p_jplt_cache);
  procedure detach_all_attc;
  procedure detach_all_curr;
  procedure detach_threads;
  function  add_plt_cache(plt:p_jit_plt;src,dst:Pointer;dest_block:p_jit_dynamic_blob):p_jplt_cache;
  function  new_chunk(count:QWORD):p_jcode_chunk;
  procedure alloc_base(_size:ptruint);
  procedure free_base;
  procedure attach_entry(node:p_jit_entry_point);
  procedure attach_all_entry;
  procedure attach_all_chunk;
  procedure attach;
  function  detach_entry(node:p_jit_entry_point):Boolean;
  procedure detach_all_entry;
  procedure detach_entry(c_start,c___end:QWORD);
  procedure detach_chunk(node:p_jcode_chunk);
  procedure detach_all_chunk;
  procedure detach;
 end;

function new_blob(_size:ptruint):p_jit_dynamic_blob;

var
 entry_hamt_lock:Pointer=nil;
 entry_hamt:TSTUB_HAMT64;

 entry_chunk_lock:Pointer=nil;

 entry_chunk_list:TAILQ_HEAD=(tqh_first:nil;tqh_last:@entry_chunk_list.tqh_first);

 //entry_chunk:t_jcode_chunk_set;

function  fetch_entry(src:Pointer):p_jit_entry_point;
function  exist_entry(src:Pointer):Boolean;

function  fetch_chunk_by_guest(src:Pointer):p_jcode_chunk;
function  fetch_blob_by_host(src:Pointer):p_jit_dynamic_blob;
function  exist_jit_host(src:Pointer;tf_tip:PQWORD):Boolean;

function  next_chunk(node:p_jcode_chunk;src:Pointer):p_jcode_chunk;
//procedure unmap_jit_cache(start,__end:QWORD);
function  preload_entry(addr:Pointer):p_jit_entry_point;

procedure jit_ctx_free(td:p_kthread);
procedure switch_to_jit(td:p_kthread);
function  jmp_dispatcher(addr:Pointer;plt:p_jit_plt;from:Pointer):Pointer;

procedure blob_track(blob:p_jit_dynamic_blob);

function  build(var ctx:t_jit_context2):p_jit_dynamic_blob;

procedure preload(addr:Pointer);

implementation

uses
 sysutils,
 vmparam,
 sys_bootparam,
 kern_proc,
 vm,
 vm_map,
 vm_pmap_prot,
 vm_pmap,
 md_map,
 vm_tracking_map,
 subr_dynlib,
 kern_dlsym;

//

procedure pick(var ctx:t_jit_context2;preload:Pointer); external name 'kern_jit_pick';

//

function scan_up_exc(addr:QWORD):QWORD;
begin
 addr:=(addr+PAGE_MASK) and QWORD(not PAGE_MASK);

 while is_guest_addr(addr) do
 begin
  Result:=addr;

  if ((ppmap_get_prot(QWORD(addr)) and PAGE_PROT_EXECUTE)=0) then
  begin
   Break;
  end;

  addr:=addr+PAGE_SIZE;
 end;

end;

function scan_dw_exc(addr:QWORD):QWORD;
begin
 addr:=addr and QWORD(not PAGE_MASK);

 while is_guest_addr(addr) do
 begin
  Result:=addr;

  if ((ppmap_get_prot(QWORD(addr)) and PAGE_PROT_EXECUTE)=0) then
  begin
   Break;
  end;

  addr:=addr-PAGE_SIZE;
 end;

end;

procedure preload(addr:Pointer);
var
 node:p_jit_entry_point;
 ctx:t_jit_context2;
begin
 if (p_print_jit_preload) then
 begin
  Writeln('unk addr:0x',HexStr(addr));
 end;

 node:=preload_entry(addr);

 if (node=nil) then
 begin
  ctx:=Default(t_jit_context2);

  ctx.text_start:=scan_dw_exc(QWORD(addr));
  ctx.text___end:=scan_up_exc(QWORD(addr));
  ctx.map____end:=ctx.text___end;

  ctx.modes     :=[cmDontScanRipRel]; //dont scan rip relative

  ctx.add_forward_point(fpCall,addr);

  pick(ctx,addr);
 end else
 begin
  node^.dec_ref('preload_entry');
 end;
end;

procedure jit_ctx_free(td:p_kthread); public;
begin
 //td^.td_jctx.block:=nil;
 kmem_free(td^.td_jctx.call_ret_cache,64*1024);
 td^.td_jctx.call_ret_cache:=nil;
end;

procedure switch_to_jit(td:p_kthread); public;
label
 _host,
 _start;
var
 node:p_jit_entry_point;
 jctx:p_td_jctx;
 frame:p_jit_frame;
begin
 if (td=nil) then Exit;

 if ((td^.pcb_flags and PCB_IS_HLE)<>0) then
 begin
  //hle mode

  goto _host;
 end else
 if ((td^.pcb_flags and PCB_IS_JIT)<>0) then
 begin
  //jit mode

  if is_guest_addr(td^.td_frame.tf_rip) then
  begin
   //jit->jit
  end else
  begin
   //jit->host

   if ((td^.td_pflags and TDP_KTHREAD)<>0) then
   begin
    //system thread

    //clear jit flag
    td^.pcb_flags:=td^.pcb_flags and (not PCB_IS_JIT);

    Exit; //internal?
   end else
   begin
    //forbidden

    Assert(false,'forbidden jump to 0x'+HexStr(td^.td_frame.tf_rip,16));
   end;

  end;

 end else
 begin
  //host mode

  _host:

  if is_guest_addr(td^.td_frame.tf_rip) then
  begin
   //host->jit
   set_pcb_flags(td,PCB_IS_JIT);
  end else
  begin
   //host->host

   Exit; //internal?
  end;

 end;

 _start:

 node:=fetch_entry(Pointer(td^.td_frame.tf_rip));

 if (node=nil) then
 begin
  preload(Pointer(td^.td_frame.tf_rip));
  goto _start;
 end;

 jctx:=@td^.td_jctx;

 frame:=@td^.td_frame.tf_r13;

 //jctx^.block:=node^.blob;

 if (jctx^.rsp=nil) then
 begin
  jctx^.rsp:=td^.td_kstack.stack;
 end;

 if (jctx^.rbp=nil) then
 begin
  jctx^.rbp:=td^.td_kstack.stack;
 end;

 if (jctx^.call_ret_cache=nil) then
 begin
  jctx^.call_ret_cache:=kmem_alloc(64*1024,VM_RW);
 end;

 //tf_r14 not need to move
 //tf_r15 not need to move

 frame^.tf_r13:=td^.td_frame.tf_r13;
 frame^.tf_rsp:=td^.td_frame.tf_rsp;
 frame^.tf_rbp:=td^.td_frame.tf_rbp;

 td^.td_frame.tf_rsp:=QWORD(td^.td_kstack.stack);
 td^.td_frame.tf_rbp:=QWORD(td^.td_kstack.stack);

 td^.td_frame.tf_rip:=QWORD(node^.dst);
 td^.td_frame.tf_r13:=QWORD(frame);

 set_pcb_flags(td,PCB_FULL_IRET or PCB_IS_JIT);

 if (td^.td_teb^.jit_trp=nil) then
 begin
  td^.td_teb^.jit_trp:=@jit_interrupt_nop;
 end;

 //teb stack
 td^.td_teb^.sttop:=td^.td_kstack.sttop;
 td^.td_teb^.stack:=td^.td_kstack.stack;
 //teb stack

 //
 node^.dec_ref('fetch_entry')
end;

function fetch_chunk_by_guest(src:Pointer):p_jcode_chunk;
var
 //i:t_jcode_chunk_set.Iterator;
 //node:t_jcode_chunk;

 obj:p_vm_track_object;
begin
 Result:=nil;

 obj:=vm_map_track_next(p_proc.p_vmspace,
                        vm_offset_t(src),
                        nil,
                        H_JIT_CHUNK);

 if (obj<>nil) then
 begin
  Result:=obj^.handle;

  if (Result<>nil) then
  begin
   Result^.inc_ref('fetch_chunk_by_guest');
  end;

  vm_track_object_deallocate(obj); //<-vm_track_map_next_object
 end;

 {
 node:=Default(t_jcode_chunk);
 node.start:=QWORD(src);

 rw_rlock(entry_chunk_lock);

 i:=entry_chunk.Find_le(@node);

 if (i.Item<>nil) then
 begin
  Result:=i.Item^;
 end;

 if (Result<>nil) then
 begin
  Result^.inc_ref;
 end;

 rw_runlock(entry_chunk_lock);
 }
end;

function next_chunk(node:p_jcode_chunk;src:Pointer):p_jcode_chunk;
var
 //i:t_jcode_chunk_set.Iterator;

 obj:p_vm_track_object;
begin
 Result:=nil;
 //

 obj:=vm_map_track_next(p_proc.p_vmspace,
                        vm_offset_t(src),
                        node^.tobj,
                        H_JIT_CHUNK);

 if (obj<>nil) then
 begin
  Result:=obj^.handle;

  if (Result<>nil) then
  begin
   Result^.inc_ref('next_chunk');
  end;

  vm_track_object_deallocate(obj); //<-vm_track_map_next_object
 end;

 {
 rw_rlock(entry_chunk_lock);

 i:=entry_chunk.find_be(node);

 if (i.Item<>nil) then
 begin
  i.Next;
 end;

 if (i.Item<>nil) then
 begin
  Result:=i.Item^;
 end;

 if (Result<>nil) then
 begin
  Result^.inc_ref;
 end;

 rw_runlock(entry_chunk_lock);
 }
end;

function fetch_blob_by_host(src:Pointer):p_jit_dynamic_blob;
var
 //i:t_jcode_chunk_set.Iterator;
 node:p_jcode_chunk;
 prev:p_jit_dynamic_blob;
 blob:p_jit_dynamic_blob;
begin
 Result:=nil;

 prev:=nil;

 //
 rw_rlock(entry_chunk_lock);

 node:=TAILQ_FIRST(@entry_chunk_list);

 //i:=entry_chunk.cbegin;

 //while (i.Item<>nil) do

 while (node<>nil) do
 begin

  //node:=i.Item^;
  //if (node<>nil) then

  if not node^.is_mark_del then
  begin
   blob:=node^.blob;
   if (blob<>nil) and (blob<>prev) then
   begin
    if blob^.cross_host(QWORD(src),QWORD(src)+1) then
    begin
     blob^.inc_ref('fetch_blob_by_host');

     rw_runlock(entry_chunk_lock);

     Exit(blob);
    end;
    prev:=blob;
   end;
  end;

  //i.Next;

  node:=TAILQ_NEXT(node,@node^.entry);
 end;

 rw_runlock(entry_chunk_lock);
end;

function exist_jit_host(src:Pointer;tf_tip:PQWORD):Boolean; public;
var
 blob:p_jit_dynamic_blob;
begin
 blob:=fetch_blob_by_host(src);
 if (blob<>nil) then
 begin
  if (tf_tip<>nil) then
  begin
   rw_rlock(blob^.lock);
   tf_tip^:=blob^.find_guest_by_host(QWORD(src));
   rw_runlock(blob^.lock);
  end;
  blob^.dec_ref('fetch_blob_by_host');
  Result:=True;
 end else
 begin
  Result:=False;
 end;
end;

{
procedure unmap_jit_cache(start,__end:QWORD); [public, alias:'kern_unmap_jit_cache'];
var
 curr,next:p_jcode_chunk;
 blob:p_jit_dynamic_blob;
begin
 curr:=fetch_chunk_by_guest(Pointer(start));
 while (curr<>nil) do
 begin

  if (curr^.start>=__end) then Break;

  if curr^.cross_guest(start,__end) then
  begin
   blob:=curr^.blob;
   rw_wlock(blob^.lock);

    blob^.detach_chunk(curr);
    blob^.detach_entry(start,__end);

   rw_wunlock(blob^.lock);
  end;

  next:=next_chunk(curr);
  curr^.dec_ref;
  curr:=next;
 end;

 if (curr<>nil) then
 begin
  curr^.dec_ref;
 end;
end;
}

function preload_entry(addr:Pointer):p_jit_entry_point;
var
 curr,next:p_jcode_chunk;
 blob:p_jit_dynamic_blob;
 dest:QWORD;
begin
 Result:=nil;

 curr:=fetch_chunk_by_guest(addr);
 while (curr<>nil) do
 begin

  //Writeln(HexStr(addr),':',HexStr(curr^.start,16),'..',HexStr(curr^.__end,16));

  //if (QWORD(addr)<curr^.start) then Break;

  dest:=curr^.find_host_by_guest(QWORD(addr));

  if (dest<>0) then
  begin
   if (p_print_jit_preload) then
   begin
    Writeln('cache:',HexStr(addr),'->',HexStr(dest,16));
   end;

   blob:=curr^.blob;

   rw_wlock(blob^.lock);
    if not curr^.is_mark_del then
    begin
     Result:=blob^.add_entry_point(addr,Pointer(dest));
    end;
   rw_wunlock(blob^.lock);

   blob^.attach_entry(Result);

   Exit;
  end;

  next:=next_chunk(curr,addr);
  curr^.dec_ref('preload_entry');
  curr:=next;
 end;

 if (curr<>nil) then
 begin
  curr^.dec_ref('preload_entry');
 end;
end;

function hash_addr(addr:Pointer):Byte; inline;
begin
 Result:=Byte(QWORD(addr) shr 4) xor Byte(QWORD(addr) shr 12);
end;

procedure ExecuteStop; assembler; nostackframe; public;
asm
 //Return JIT->HLE
end;

const
 EXECUTE_MAGIC_ADDR:QWORD=QWORD($FFFFFFFFFCAFEDAD);

function jmp_dispatcher(addr:Pointer;plt:p_jit_plt;from:Pointer):Pointer; public;
label
 _start;
var
 td   :p_kthread;
 node :p_jit_entry_point;
 jctx :p_td_jctx;
 curr :p_jit_dynamic_blob;
 cache:p_jplt_cache;
begin
 td:=curkthread;
 if (td=nil) then Exit(nil);

 //jit_state:=((td^.pcb_flags and PCB_IS_JIT)<>0);

 if not is_guest_addr(QWORD(addr)) then
 begin
  //switch to internal

  if (QWORD(addr)=EXECUTE_MAGIC_ADDR) then
  begin
   Exit(@ExecuteStop);
  end else
  if ((QWORD(addr) and UNRESOLVE_MAGIC_MASK)=UNRESOLVE_MAGIC_ADDR) then
  begin
   if exist_jit_host(from,@td^.td_frame.tf_rip) then
   begin
    test_unresolve_symbol(td,addr);
   end;
  end;

  writeln('not guest addr:0x',HexStr(addr));
  Assert(False,'TODO');

  //td^.td_teb^.jitcall:=addr;
  //Exit(@jit_jmp_internal);
 end;

 _start:

 if ((ppmap_get_prot(QWORD(addr)) and PAGE_PROT_EXECUTE)=0) then
 begin
  writeln('not excec:0x',HexStr(addr));
  Assert(False,'TODO');
 end;

 jctx:=@td^.td_jctx;

 cache:=jctx^.local_cache[hash_addr(addr)];

 if (cache<>nil) then
 begin
  if (cache^.src=addr) then
  begin
   //jctx^.block:=cache^.blk;

   Result:=cache^.dst;

   if (InterlockedExchangeAdd64(QWORD(cache^.dest_block),0)=0) then
   begin
    //reset all
    cache:=nil;
    Result:=nil;
    jctx^.local_cache[hash_addr(addr)]:=nil;
   end else
   begin
    Exit;
   end;

  end else
  begin
   cache:=nil;
  end;
 end;

 node:=fetch_entry(addr);

 if (node=nil) then
 begin
  preload(addr);
  goto _start;
 end;

 //jctx:=@td^.td_jctx;

 //curr:=jctx^.block;
 //curr:=fetch_blob_by_host(plt);

 //curr:=plt^.block;

 if (plt<>nil) then
 begin
  cache:=plt^.cache;
  curr:=cache^.self_block;
 end else
 begin
  curr:=nil;
 end;

 if (curr=nil) or (plt=nil) then
 begin
  //jctx^.block:=node^.blob;
 end else
 begin
  cache:=curr^.add_plt_cache(plt,node^.src,node^.dst,node^.blob);

  jctx^.local_cache[hash_addr(addr)]:=cache;

  //jctx^.block:=node^.blob;

  Assert(cache<>nil);
  Assert(cache^.src<>nil);
  Assert(cache^.dst<>nil);

  //one element plt cache
  System.InterlockedExchange(plt^.cache,cache);
 end;

 Result:=node^.dst;

 //
 node^.dec_ref('fetch_entry');
end;

function t_jcode_chunk.c(n1,n2:p_jcode_chunk):Integer;
begin
 Result:=Integer(n1^.start>n2^.start)-Integer(n1^.start<n2^.start);
 if (Result<>0) then Exit;
 Result:=Integer(n1^.hash>n2^.hash)-Integer(n1^.hash<n2^.hash);
end;

function t_jplt_cache.c(n1,n2:p_jplt_cache):Integer;
begin
 Result:=Integer(n1^.plt>n2^.plt)-Integer(n1^.plt<n2^.plt);
 if (Result<>0) then Exit;
 Result:=Integer(n1^.src>n2^.src)-Integer(n1^.src<n2^.src);
end;

procedure build_chunk(var ctx:t_jit_context2;blob:p_jit_dynamic_blob;start,__end,count:QWORD);
var
 hash :QWORD;

 i       :QWORD;
 original:QWORD;
 recompil:QWORD;

 jcode:p_jcode_chunk;
 table:p_jinstr_len;

 clabel:t_jit_context2.p_label;

 link_prev:t_jit_i_link;
 link_curr:t_jit_i_link;
 link_next:t_jit_i_link;

 prev:Pointer;
 curr:Pointer;
 next:Pointer;
begin
 jcode:=nil;
 table:=nil;

 if (count=0) then Exit;

 hash:=MurmurHash64A(uplift(Pointer(start)),__end-start,$010CA1C0DE);

 clabel:=ctx.get_label(Pointer(start));

 jcode:=blob^.new_chunk(count);

 jcode^.start:=start;
 jcode^.__end:=__end;
 jcode^.dest :=QWORD(blob^.base)+clabel^.link_curr.offset;
 jcode^.hash :=hash ;

 table:=@jcode^.table;

 i:=0;
 curr:=Pointer(start);

 prev:=nil;
 link_prev:=nil_link;

 //get table
 while (QWORD(curr)<__end) do
 begin
  clabel:=ctx.get_label(curr);

  next:=clabel^.next;

  link_curr:=clabel^.link_curr;
  link_next:=clabel^.link_next;

  if (link_prev<>nil_link) then
  begin
   if (link_prev.offset<>link_curr.offset) then
   begin
    Writeln('oaddr:',HexStr(curr),'..',HexStr(next),' prev:',HexStr(prev));
    Writeln('table:',HexStr(blob^.base+link_prev.offset),'<>',HexStr(blob^.base+link_curr.offset));

    print_disassemble(blob^.base+link_prev.offset,link_next.offset-link_prev.offset);

    Assert(False);
   end;
  end;

  original:=QWORD(next)-QWORD(curr);
  recompil:=link_next.offset-link_curr.offset;

  if (original>16) or (recompil>512) then
  begin
   Writeln('0x',HexStr(curr));
   Writeln(original,':',recompil);
   Assert(False);
  end;

  table[i].original:=Byte(original);
  table[i].recompil:=Byte(recompil);

  table[i].LF_JMP  :=ord((clabel^.flags and LF_JMP)<>0);

  {
  writeln('|0x',HexStr(curr),'..',HexStr(next),
          ':0x',HexStr(link_curr.offset,8),'..',HexStr(link_next.offset,8),
          ':',i);
  }

  prev:=curr;
  link_prev:=link_next;

  Inc(i);
  curr:=next;
 end;

 //get last addr
 //jcode^.d_end:=QWORD(blob^.base)+clabel^.link_curr.offset;

 recompil:=0;
 for i:=0 to count-1 do
 begin
  recompil:=recompil+table[i].recompil;
 end;
 jcode^.d_end:=QWORD(jcode^.dest)+recompil;

 if (p_print_jit_preload) then
 begin
  Writeln('build_chunk:0x',HexStr(jcode^.dest,16),'..',HexStr(jcode^.d_end,16),':',count);
 end;

 //writeln('[0x',HexStr(start,16),':0x',HexStr(__end,16),':',count);
end;

procedure build_blob(var ctx:t_jit_context2;
                     blob:p_jit_dynamic_blob;
                     start,__end:QWORD);
var
 count:QWORD;

 clabel:t_jit_context2.p_label;

 link_prev:t_jit_i_link;
 link_curr:t_jit_i_link;
 link_next:t_jit_i_link;

 prev:QWORD;
 curr:QWORD;
 next:QWORD;
begin
 count:=0;
 curr:=start;

 prev:=0;
 link_prev:=nil_link;

 //get count
 while (QWORD(curr)<__end) do
 begin
  clabel:=ctx.get_label(Pointer(curr));

  if (clabel=nil) then
  begin
   Writeln('(clabel=nil) 0x',HexStr(curr,16));
   Assert(false);
  end;

  //Writeln('clabel:0x',HexStr(QWORD(blob^.base)+clabel^.link_curr.offset,16));

  next:=QWORD(clabel^.next);

  link_curr:=clabel^.link_curr;
  link_next:=clabel^.link_next;

  if (link_prev<>nil_link) then
  begin
   if (link_prev.offset<>link_curr.offset) then
   begin
    //devide chunk

    build_chunk(ctx,blob,start,curr,count);

    start:=curr;
    count:=0;
   end;
  end;

  prev:=curr;
  link_prev:=link_next;

  Inc(count);
  curr:=next;
 end;

 build_chunk(ctx,blob,start,__end,count);
end;

function build(var ctx:t_jit_context2):p_jit_dynamic_blob;
var
 addr:Pointer;

 blob:p_jit_dynamic_blob;
 entry_point:t_jit_context2.p_entry_point;

 chunk:p_jit_code_chunk;

 start:QWORD;
 __end:QWORD;

 //F:THandle;

begin
 Result:=nil;

 if (ctx.builder.GetMemSize=0) then Exit;

 blob:=new_blob(ctx.builder.GetMemSize);

 ctx.builder.SaveTo(blob^.base,ctx.builder.GetMemSize);

 blob^.plta:=blob^.base+ctx.builder.GetPltStart;
 blob^.pltc:=ctx.builder.APltCount;

 blob^.init_plt;

 if (p_print_jit_preload) then
 begin
  Writeln('build:0x',HexStr(ctx.text_start,16),'->0x',HexStr(blob^.base),'..',HexStr(blob^.base+blob^.size));
 end;

 //F:=FileCreate('recompile.bin');
 //FileWrite(F,blob^.base^,ctx.builder.GetMemSize);
 //FileClose(F);

 //copy entrys
 entry_point:=ctx.entry_list;
 while (entry_point<>nil) do
 begin
  addr:=blob^.base+entry_point^.instruction.offset;
  //
  blob^.add_entry_point(entry_point^.src,addr);
  //
  entry_point:=entry_point^.next;
 end;

 start:=0;
 __end:=0;

 //copy chunks
 chunk:=TAILQ_FIRST(@ctx.builder.ACodeChunkList);

 while (chunk<>nil) do
 begin
  if (chunk^.start=chunk^.__end) then
  begin
   //no instr
  end else
  begin
   //
   {if (t_point_type(chunk^.data)=fpInvalid) then
   begin
    //skip
   end else }
   if (__end=chunk^.start) then
   begin
    //expand
    __end:=chunk^.__end;
   end else
   begin
    //save
    if (start<>0) then
    begin
     //build prev saved
     build_blob(ctx,blob,start,__end);
    end;
    //new
    start:=chunk^.start;
    __end:=chunk^.__end;
   end;
  end;
  //
  chunk:=TAILQ_NEXT(chunk,@chunk^.entry);
 end;

 if (start<>0) then
 begin
  //build prev saved
  build_blob(ctx,blob,start,__end);
 end;

 //blob^.attach;

 Result:=blob;
end;

function fetch_entry(src:Pointer):p_jit_entry_point;
var
 data:PPointer;
begin
 Result:=nil;
 rw_rlock(entry_hamt_lock);

 data:=HAMT_search64(@entry_hamt,QWORD(src));
 if (data<>nil) then
 begin
  Result:=data^;
 end;

 if (Result<>nil) then
 begin
  Result^.inc_ref('fetch_entry');
 end;

 rw_runlock(entry_hamt_lock);
end;

function exist_entry(src:Pointer):Boolean;
var
 entry:p_jit_entry_point;
begin
 entry:=fetch_entry(src);
 if (entry<>nil) then
 begin
  entry^.dec_ref('fetch_entry');
  Result:=True;
 end else
 begin
  Result:=False;
 end;
end;

//

function new_blob(_size:ptruint):p_jit_dynamic_blob;
begin
 Result:=AllocMem(SizeOf(t_jit_dynamic_blob));
 Result^.alloc_base(_size);

 TAILQ_INIT(@Result^.jpltc_attc);
end;

//

procedure t_jit_entry_point.inc_ref(name:pchar);
begin
 blob^.inc_ref(name);
end;

procedure t_jit_entry_point.dec_ref(name:pchar);
begin
 blob^.dec_ref(name);
end;


//

procedure t_jcode_chunk.inc_ref(name:pchar);
begin
 blob^.inc_ref(name);
end;

procedure t_jcode_chunk.dec_ref(name:pchar);
begin
 blob^.dec_ref(name);
end;

function t_jcode_chunk.is_mark_del:Boolean;
begin
 Result:=(tobj=nil);
end;

//

procedure t_jit_dynamic_blob.inc_ref(name:pchar);
begin
 //Writeln('inc_ref:0x',HexStr(@self),' ',name);

 System.InterlockedIncrement(refs);
end;

procedure t_jit_dynamic_blob.dec_ref(name:pchar);
begin
 //Writeln('dec_ref:0x',HexStr(@self),' ',name);

 if (System.InterlockedDecrement(refs)=0) then
 begin
  //Writeln('free:',HexStr(@self),' ',name);
  Free;
 end;
end;

procedure t_jit_dynamic_blob.inc_attach_count;
begin
 System.InterlockedIncrement(attach_count);
end;

function t_jit_dynamic_blob.dec_attach_count:Boolean;
begin
 Result:=(System.InterlockedDecrement(attach_count)=0);
end;

function t_jit_dynamic_blob.find_guest_by_host(addr:QWORD):QWORD;
var
 node:p_jcode_chunk;
begin
 //Writeln('_ind_guest_by_host:0x',HexStr(base),' 0x',HexStr(base+size),' 0x',HexStr(addr,16));

 Result:=0;
 node:=chunk_list;
 while (node<>nil) do
 begin
  Result:=node^.find_guest_by_host(addr);
  if (Result<>0) then Exit;

  node:=node^.next;
 end;
end;

function t_jit_dynamic_blob.cross_host(c_start,c___end:QWORD):Boolean;
begin
 Result:=(c___end>QWORD(base)) and (c_start<(QWORD(base)+size));
end;

//

procedure t_jit_dynamic_blob.Free;
var
 node,next:p_jit_entry_point;
begin
 node:=entry_list;
 while (node<>nil) do
 begin
  next:=node^.next;
  free_entry_point(node);
  node:=next;
 end;

 free_base;

 //TODO: GC FREE
 FreeMem(@Self);
end;

function t_jit_dynamic_blob.add_entry_point(src,dst:Pointer):p_jit_entry_point;
begin
 if (src=nil) or (dst=nil) then Exit;
 Result:=AllocMem(Sizeof(t_jit_entry_point));
 Result^.next:=entry_list;
 Result^.blob:=@Self;
 Result^.src :=src;
 Result^.dst :=dst;
 //
 entry_list:=Result;
end;

procedure t_jit_dynamic_blob.free_entry_point(node:p_jit_entry_point);
begin
 FreeMem(node);
end;

procedure t_jit_dynamic_blob.init_plt;
var
 i:Integer;
begin
 if (pltc<>0) then
 begin
  plt_stub.self_block:=@Self;
  plt_stub.dest_block:=@Self;

  For i:=0 to pltc-1 do
  begin
   plta[i].cache:=@plt_stub;
   //plta[i].block:=@Self;
  end;

 end;
end;

procedure t_jit_dynamic_blob.attach_plt_cache(uplock:p_jit_dynamic_blob;node:p_jplt_cache);
begin
 Self.inc_ref('attach_plt_cache');

 if (uplock<>@Self) then
 begin
  rw_wlock(lock);
 end;

 TAILQ_INSERT_TAIL(@jpltc_attc,node,@node^.entry);

 if (uplock<>@Self) then
 begin
  rw_wunlock(lock);
 end;
end;

procedure t_jit_dynamic_blob.detach_plt_cache(uplock:p_jit_dynamic_blob;node:p_jplt_cache);
begin
 if (node^.entry.tqe_prev<>nil) then
 begin
  if (uplock<>@Self) then
  begin
   rw_wlock(lock);
  end;

  TAILQ_REMOVE(@jpltc_attc,node,@node^.entry);

  node^.entry:=Default(TAILQ_ENTRY);

  if (uplock<>@Self) then
  begin
   rw_wunlock(lock);
  end;

  Self.dec_ref('attach_plt_cache');
 end;
end;

procedure reset_plt(node:p_jplt_cache);
var
 blk:p_jit_dynamic_blob;
 plt:p_jit_plt;
begin
 blk:=node^.self_block;
 plt:=node^.plt;
 if (plt<>nil) and (blk<>nil) then
 begin
  //one element plt reset
  System.InterlockedCompareExchange(plt^.cache,@blk^.plt_stub,node);
 end;
end;

procedure t_jit_dynamic_blob.detach_all_attc;
var
 node,next:p_jplt_cache;
begin
 node:=TAILQ_FIRST(@jpltc_attc);

 while (node<>nil) do
 begin
  next:=TAILQ_NEXT(node,@node^.entry);

  TAILQ_REMOVE(@jpltc_attc,node,@node^.entry);

  node^.entry:=Default(TAILQ_ENTRY);

  reset_plt(node);

  //force deref
  if (System.InterlockedCompareExchange(node^.dest_block,nil,@Self)=@Self) then
  begin
   Self.dec_ref('add_plt_cache');
  end;

  node:=next;
 end;

 if TAILQ_FIRST(@jpltc_attc)<>nil then
 begin
  Assert(false);
 end;

end;

procedure t_jit_dynamic_blob.detach_all_curr;
var
 node:p_jplt_cache;
 blk :p_jit_dynamic_blob;
begin
 node:=jpltc_curr.Min;

 while (node<>nil) do
 begin
  jpltc_curr.Delete(node);

  reset_plt(node);

  blk:=System.InterlockedExchange(node^.dest_block,nil);

  if (blk<>nil) then
  begin
   blk^.detach_plt_cache(@Self,node);
  end;

  //TODO: GC FREE
  FreeMem(node);

  node:=jpltc_curr.Min;
 end;
end;

procedure t_jit_dynamic_blob.detach_threads;
var
 ttd:p_kthread;
 call_ret_cache:PQWORD;
 bend:QWORD;
 src:QWORD;
 i:Integer;
begin
 bend:=QWORD(base)+size;

 threads_lock;

   ttd:=TAILQ_FIRST(get_p_threads);
   while (ttd<>nil) do
   begin

    //
    call_ret_cache:=ttd^.td_jctx.call_ret_cache;

    if (call_ret_cache<>nil) then
    For i:=0 to (64*1024 div 16)-1 do
    begin
     src:=-call_ret_cache[i*2]; //-(-src)

     if (src>=QWORD(base)) and (src<bend) then
     begin
      System.InterlockedCompareExchange64(call_ret_cache[i*2],0,-src);
     end;

    end;
    //

    //
    for i:=0 to High(ttd^.td_jctx.local_cache) do
    begin
     src:=QWORD(ttd^.td_jctx.local_cache[i]);

     if (src>=QWORD(base)) and (src<bend) then
     begin
      System.InterlockedCompareExchange64(QWORD(ttd^.td_jctx.local_cache[i]),0,src);
     end;

    end;
    //

    ttd:=TAILQ_NEXT(ttd,@ttd^.td_plist)
   end;

 threads_unlock;
end;

function t_jit_dynamic_blob.add_plt_cache(plt:p_jit_plt;src,dst:Pointer;dest_block:p_jit_dynamic_blob):p_jplt_cache;
var
 node:t_jplt_cache;
 old_blk:p_jit_dynamic_blob;
 _insert:Boolean;
begin
 Assert(plt<>nil);
 Assert(dest_block<>nil);

 node.plt:=plt; //key
 node.src:=src; //key
 node.neg:=Pointer(-QWORD(src));

 repeat

  old_blk:=nil;

  //high threads concurrency
  rw_wlock(lock);
   Result:=jpltc_curr.Find(@node);
   if (Result<>nil) then
   begin
    //update
    Result^.dst:=dst;
    //
    old_blk:=System.InterlockedExchange(Result^.dest_block,dest_block);
    if (old_blk<>dest_block) then
    begin
     if (old_blk<>nil) and (old_blk=@Self) then
     begin
      //detach immediately
      old_blk^.detach_plt_cache(@Self,Result);
     end;
     if (dest_block=@Self) then
     begin
      //attach immediately
      dest_block^.attach_plt_cache(@Self,Result);
     end;
    end;
   end;
  rw_wunlock(lock);

  if (Result<>nil) then
  begin
   if (old_blk<>dest_block) then
   begin
    if (old_blk<>nil) and (old_blk<>@Self) then
    begin
     //detach deferred
     old_blk^.detach_plt_cache(@Self,Result);
    end;
    //
    if (dest_block<>@Self) then
    begin
     //attach deferred
     dest_block^.attach_plt_cache(@Self,Result);
    end;
   end;
   //
   Break;
  end else
  begin
   Result:=AllocMem(Sizeof(t_jplt_cache));
   Result^.plt:=plt; //key
   Result^.src:=src; //key
   Result^.neg:=Pointer(-QWORD(src));
   Result^.dst:=dst;
   Result^.self_block:=@Self;
   Result^.dest_block:=dest_block;
   //
   rw_wlock(lock);
    _insert:=jpltc_curr.Insert(Result);
    if _insert and (dest_block=@Self) then
    begin
     //attach immediately
     dest_block^.attach_plt_cache(@Self,Result);
    end;
   rw_wunlock(lock);
   //
   if _insert then
   begin
    //attach deferred
    if (dest_block<>@Self) then
    begin
     dest_block^.attach_plt_cache(@Self,Result);
    end;
    //
    Break;
   end else
   begin
    FreeMem(Result);
    Result:=nil;
   end;
  end;

 until false;

end;

function t_jit_dynamic_blob.new_chunk(count:QWORD):p_jcode_chunk;
begin
 Result:=AllocMem(SizeOf(t_jcode_chunk)+SizeOf(t_jinstr_len)*count);
 Result^.count:=count;
 Result^.blob :=@Self;
 //
 Result^.next:=chunk_list;
 chunk_list:=Result;
end;

procedure t_jit_dynamic_blob.alloc_base(_size:ptruint);
begin
 base:=nil;
 size:=_size;

 ///md_mmap(base,size,VM_RWX);

 mchunk:=p_alloc(nil,_size,False);
 base:=@mchunk^.body;
end;

procedure t_jit_dynamic_blob.free_base;
begin
 p_free(mchunk);

 ////md_unmap(base,size);

 base:=nil;
 size:=0;
end;

//

function t_jcode_chunk.find_host_by_guest(addr:QWORD):QWORD;
var
 i,src,dst:QWORD;
 _table:p_jinstr_len;
begin
 Result:=0;
 if (addr>=start) and (addr<__end) then
 if (count<>0) then
 begin
  src:=start;
  dst:=dest;
  _table:=@table;
  For i:=0 to count-1 do
  begin

   if (src=addr) then
   begin
    Exit(dst);
   end else
   if (src>addr) then
   begin
    Exit(0);
   end;

   src:=src+_table[i].original;
   dst:=dst+_table[i].recompil;
  end;
 end;
end;

function t_jcode_chunk.find_guest_by_host(addr:QWORD):QWORD;
var
 i,src,dst:QWORD;
 _table:p_jinstr_len;
begin
 Result:=0;
 //Writeln('find_guest_by_host:0x',HexStr(dest,16),' 0x',HexStr(d_end,16),' 0x',HexStr(addr,16));
 if (addr>=dest) and (addr<=d_end) then
 if (count<>0) then
 begin
  src:=start;
  dst:=dest;
  _table:=@table;
  For i:=0 to count-1 do
  begin

   if (addr>=dst) then
   begin
    Result:=src;
   end else
   if (dst>addr) then
   begin
    Exit;
   end;

   src:=src+_table[i].original;
   dst:=dst+_table[i].recompil;
  end;
 end;
end;

function t_jcode_chunk.cross_guest(c_start,c___end:QWORD):Boolean;
begin
 Result:=(c___end>start) and (c_start<__end);
end;

function t_jcode_chunk.cross_host(c_start,c___end:QWORD):Boolean;
begin
 Result:=(c___end>dest) and (c_start<d_end);
end;

//

procedure t_jit_dynamic_blob.attach_entry(node:p_jit_entry_point);
var
 data:PPointer;
 old:p_jit_entry_point;
begin
 node^.inc_ref('attach_entry');
 self.inc_attach_count;

 old:=nil;

 rw_wlock(entry_hamt_lock);
  data:=HAMT_insert64(@entry_hamt,QWORD(node^.src),node);
  Assert(data<>nil);
  if (data^<>node) then
  begin
   old:=data^;
   data^:=node;
  end;
  node^.entry_public:=1;
 rw_wunlock(entry_hamt_lock);
end;

procedure t_jit_dynamic_blob.attach_all_entry;
var
 node,next:p_jit_entry_point;
begin
 node:=entry_list;
 while (node<>nil) do
 begin
  next:=node^.next;

  attach_entry(node);

  node:=next;
 end;
end;

procedure t_jit_dynamic_blob.attach_all_chunk;
//var
// node,next:p_jcode_chunk;
begin
 blob_track(@self);

 {
 node:=chunk_list;
 while (node<>nil) do
 begin
  next:=node^.next;

  node^.inc_ref;

  rw_wlock(entry_chunk_lock);
   entry_chunk.Insert(node);
  rw_wunlock(entry_chunk_lock);

  node:=next;
 end;
 }
end;

procedure t_jit_dynamic_blob.attach;
begin
 attach_all_entry;
 attach_all_chunk;
end;

function t_jit_dynamic_blob.detach_entry(node:p_jit_entry_point):Boolean;
var
 old:p_jit_entry_point;
begin
 if (node^.entry_public=0) then Exit;

 old:=nil;

 rw_wlock(entry_hamt_lock);
  HAMT_delete64(@entry_hamt,QWORD(node^.src),@old);
 rw_wunlock(entry_hamt_lock);

 if (old=node) then
 begin
  if (System.InterlockedExchange(node^.entry_public,0)<>0) then
  begin
   Result:=self.dec_attach_count;
   node^.dec_ref('attach_entry');
  end;
 end;
end;

procedure t_jit_dynamic_blob.detach_all_entry;
var
 node,next:p_jit_entry_point;
begin
 node:=entry_list;
 while (node<>nil) do
 begin
  next:=node^.next;

  detach_entry(node);

  node:=next;
 end;
end;

procedure t_jit_dynamic_blob.detach_entry(c_start,c___end:QWORD);
var
 node,next:p_jit_entry_point;
 s:QWORD;
begin
 node:=entry_list;
 while (node<>nil) do
 begin
  next:=node^.next;

  s:=QWORD(node^.src);
  if (s>=c_start) and (s<c___end) then
  begin
   detach_entry(node);
  end;

  node:=next;
 end;
end;

procedure t_jit_dynamic_blob.detach_chunk(node:p_jcode_chunk);
var
 tobj:p_vm_track_object;
 removed:Boolean;
begin
 tobj:=node^.tobj;

 if (tobj<>nil) then
 begin
  _vm_map_track_delete_deferred(p_proc.p_vmspace,tobj);
 end;

 removed:=False;

 if (node^.entry.tqe_next<>nil) and
    (node^.entry.tqe_prev<>nil) then
 begin
  rw_wlock(entry_chunk_lock);
  if (node^.entry.tqe_next<>nil) and
     (node^.entry.tqe_prev<>nil) then
  begin
   TAILQ_REMOVE(@entry_chunk_list,node,@node^.entry);
   node^.entry:=Default(TAILQ_ENTRY);
   removed:=True;
  end;
  rw_wunlock(entry_chunk_lock);
 end;

 if removed then
 begin
  node^.dec_ref('blob_track');
 end;
end;

procedure t_jit_dynamic_blob.detach_all_chunk;
var
 node,next:p_jcode_chunk;
begin
 node:=chunk_list;
 while (node<>nil) do
 begin
  next:=node^.next;

  detach_chunk(node);

  node:=next;
 end;
end;

procedure t_jit_dynamic_blob.detach;
begin
 inc_ref('detach');

 rw_wlock(lock);

 detach_all_entry;
 detach_all_attc;
 detach_all_curr;
 detach_threads;
 detach_all_chunk;

 rw_wunlock(lock);

 dec_ref('detach');
end;

procedure delete_jit_cache(chunk:p_jcode_chunk);
var
 blob:p_jit_dynamic_blob;
begin
 if (chunk=nil) then Exit;

 //delete full in blob
 blob:=chunk^.blob;
 blob^.detach;

 chunk^.dec_ref('tobj');
end;

function on_destroy(handle:Pointer):Integer;
var
 tobj:Pointer;
begin
 //Writeln('on_destroy:',HexStr(handle));

 tobj:=System.InterlockedExchange(p_jcode_chunk(handle)^.tobj,nil); //mark delete

 if (tobj=nil) then
 begin
  Exit(DO_DELETE);
 end;

 delete_jit_cache(p_jcode_chunk(handle));

 Result:=DO_DELETE;
end;

function on_trigger(handle:Pointer;mode:T_TRIGGER_MODE):Integer;
begin
 Result:=DO_NOTHING;

 case mode of
  M_CPU_WRITE :;
  M_DMEM_WRITE:;
  else
   Exit;
 end;

 //TODO
 Result:=DO_INCREMENT;
end;

procedure blob_track(blob:p_jit_dynamic_blob);
var
 node:p_jcode_chunk;
 tobj:p_vm_track_object;
begin
 node:=blob^.chunk_list;

 while (node<>nil) do
 begin
  if (node^.start<>node^.__end) then
  begin

   node^.inc_ref('blob_track');

   rw_wlock(entry_chunk_lock);
    TAILQ_INSERT_TAIL(@entry_chunk_list,node,@node^.entry);
   rw_wunlock(entry_chunk_lock);

   node^.inc_ref('tobj');

   tobj:=vm_track_object_allocate(node,node^.start,node^.__end,H_JIT_CHUNK,PAGE_TRACK_W);
   tobj^.on_destroy:=@on_destroy;
   tobj^.on_trigger:=@on_trigger;

   node^.tobj:=tobj;

   vm_map_track_insert(p_proc.p_vmspace,tobj);

   vm_track_object_deallocate(tobj); //<-vm_track_object_allocate
  end;
  //
  node:=node^.next;
 end;

end;


end.






