unit srOpUtils;

{$mode objfpc}{$H+}

interface

uses
 spirv,
 srNode,
 srOp,
 srReg,
 srLayout,
 srVariable,
 srCFGParser;

function  InsSpirvOp(pLine,pNew:TSpirvOp):TSpirvOp;
Function  get_inverse_left_cmp_op(OpId:DWORD):DWORD;
Function  get_inverse_not_cmp_op(OpId:DWORD):DWORD;
Function  is_term_op(OpId:DWORD):Boolean;
Function  is_merge_op(OpId:DWORD):Boolean;
Function  is_term_op(pLine:TSpirvOp):Boolean;
Function  get_next_term_op(pLine:TSpirvOp):TSpirvOp;
procedure up_merge_line(var pLine:TSpirvOp);
function  FindUpSameOp(pLine,node:TSpirvOp):TSpirvOp;
function  IsDominUp(pNodeUp,pLine:TSpirvOp):Boolean;

function  GetGlobalIndex(pLine:TSpirvOp):DWORD;
function  GetGlobalIndexA(pLine:TSpirvOp):DWORD;
function  MaxLine(p1,p2:TSpirvOp):TSpirvOp;
function  MinLine(p1,p2:TSpirvOp):TSpirvOp;
Function  IsParentOf(pLine,pCurr:TsrOpBlock):Boolean;
Function  IsParentOfLine(pLine,pCurr:TSpirvOp):Boolean;
function  GetMaxPlace(pLine:TSpirvOp;count:Byte;src:PPsrRegNode):TSpirvOp;

function  GetChainRegNode (node:TsrRegNode):TsrChain;
function  GetSourceRegNode(node:TsrRegNode):TsrNode;

function  RegDownTestError(node:TsrRegNode):TsrRegNode;

function  flow_down_next_up(pLine:TSpirvOp):TSpirvOp;
function  flow_down_prev_up(pLine:TSpirvOp):TSpirvOp;
function  flow_prev_up(pLine:TSpirvOp):TSpirvOp;

implementation

uses
 srPrivate;

//--

function flow_down_next_up(pLine:TSpirvOp):TSpirvOp;
begin
 Result:=pLine.First; //down
 if (Result=nil) then
 begin
  repeat //up
   Result:=pLine.Next;
   pLine:=pLine.Parent;
  until (pLine=nil) or (Result<>nil);
 end;
end;

function flow_down_prev_up(pLine:TSpirvOp):TSpirvOp;
begin
 Result:=pLine.Last; //down
 if (Result=nil) then
 begin
  repeat //up
   Result:=pLine.Prev;
   pLine:=pLine.Parent;
  until (pLine=nil) or (Result<>nil);
 end;
end;

function flow_prev_up(pLine:TSpirvOp):TSpirvOp;

 function _last(p:TSpirvOp):TSpirvOp;
 begin
  Result:=nil;
  if (p<>nil) then
  if p.IsType(ntOpBlock) then
  if not IsReal(TsrOpBlock(p).bType) then
  begin
   Result:=p.Last;
  end;
 end;

begin
 Result:=_last(pLine); //down

 if (Result=nil) then
 begin
  repeat //up
   Result:=pLine.Prev;
   pLine:=pLine.Parent;
  until (pLine=nil) or (Result<>nil);
 end;
end;

function InsSpirvOp(pLine,pNew:TSpirvOp):TSpirvOp;
var
 tmp:TSpirvOp;
begin
 Result:=nil;
 Assert(pLine<>nil);
 if (pLine=nil) or (pNew=nil) then Exit;

 if (pLine.Next<>nil) then
 if pNew.IsType(ntOp) then
 begin
  tmp:=pLine;
  while (not tmp.IsType(ntOp)) do
  begin
   tmp:=flow_down_prev_up(tmp);
   Assert(tmp<>nil);
  end;
  //pNew.Adr:=tmp.Adr;
 end;

 pLine.InsertAfter(pNew);
 Result:=pNew;
end;

function GetCurrentIndex(pLine:TSpirvOp):DWORD;
var
 pParent:TsrOpBlock;
 node:TSpirvOp;
begin
 Result:=0;
 if (pLine=nil) then Exit;
 pParent:=pLine.Parent;
 if (pParent=nil) then Exit;
 node:=pParent.First;
 While (node<>nil) and (node<>pLine) do
 begin
  Result:=Result+node.GetIndexCount;
  node:=node.Next;
 end;
end;

function GetGlobalIndex(pLine:TSpirvOp):DWORD;
var
 pParent:TsrOpBlock;
 node:TSpirvOp;
begin
 Result:=0;
 if (pLine=nil) then Exit;
 node:=pLine;
 pParent:=node.Parent;
 While (pParent<>nil) do
 begin
  Result:=Result+GetCurrentIndex(node);
  node:=pParent;
  pParent:=node.Parent;
 end;
end;

function GetGlobalIndexA(pLine:TSpirvOp):DWORD;
begin
 Result:=pLine.GetIndexCount+GetGlobalIndex(pLine);
end;

function isGTLine(p1,p2:TSpirvOp):Boolean; //(p1>p2)
begin
 Result:=False;
 p2:=p2.Next;
 While (p2<>nil) do
 begin
  if (p1=p2) then Exit(True);
  p2:=p2.Next;
 end;
end;

Function IsGTLevel(p1,p2:TSpirvOp):Boolean; //(p1>p2)
var
 pParent1:TsrOpBlock;
 pParent2:TsrOpBlock;
begin
 Result:=False;
 pParent1:=p1.Parent;
 pParent2:=p2.Parent;
 Result:=(pParent1.Level>pParent2.Level);
end;

function MaxLine(p1,p2:TSpirvOp):TSpirvOp;
var
 pParent:TsrOpBlock;
 node:TSpirvOp;
 i,w:DWORD;
begin
 Result:=nil;
 if (p1=nil) then Exit(p2);
 if (p2=nil) or (p1=p2) then Exit(p1);

 if IsGTLevel(p2,p1) then //(p2>p1)
 begin
  //swap
  node:=p1;
  p1:=p2;
  p2:=node;
 end;

 i:=0;
 node:=p1;
 pParent:=node.Parent;
 While (pParent<>nil) do
 begin
  if (pParent=p2.Parent) then
  begin
   if isGTLine(node,p2) then //(node>p2)
   begin
    //Assert(not isGTLine(p2,node));
    Exit(p1);
   end else
   begin
    //Assert(isGTLine(p2,node));
    Exit(p2);
   end;
  end;
  i:=i+GetCurrentIndex(node);
  node:=pParent;
  pParent:=node.Parent;
 end;

 w:=GetGlobalIndex(p2);
 if (i>w) then
 begin
  Result:=p1;
 end else
 begin
  Result:=p2;
 end;
end;

function MinLine(p1,p2:TSpirvOp):TSpirvOp;
begin
 if (MaxLine(p1,p2)=p1) then
 begin
  Result:=p2;
 end else
 begin
  Result:=p1;
 end;
end;

Function IsParentOf(pLine,pCurr:TsrOpBlock):Boolean;
begin
 Result:=False;
 if not pLine.IsType(ntOpBlock) then Exit;
 if not pCurr.IsType(ntOpBlock) then Exit;
 While (pLine<>nil) do
 begin
  if (pLine=pCurr) then Exit(True);
  pLine:=pLine.Parent;
 end;
end;

Function IsParentOfLine(pLine,pCurr:TSpirvOp):Boolean;
begin
 Result:=IsParentOf(pLine.Parent,pCurr.Parent);
end;

function GetMaxPlace(pLine:TSpirvOp;count:Byte;src:PPsrRegNode):TSpirvOp;
var
 m:TSpirvOp;
 i:Byte;
begin
 Result:=pLine;
 if (count=0) or (src=nil) then Exit;
 m:=nil;
 For i:=0 to count-1 do
 begin
  if (not src[i].is_const) then
  //Dependencies can be in grouping blocks
  //if IsParentOfLine(pLine,src[i].pLine) then
  begin
   m:=MaxLine(m,src[i].pLine);
  end;
 end;
 if (m<>nil) then
 begin
  Result:=m;
 end;
end;

Function get_inverse_left_cmp_op(OpId:DWORD):DWORD;
begin
 Result:=0;
 Case OpId of
  Op.OpFOrdLessThan          :Result:=Op.OpFOrdGreaterThan       ;
  Op.OpFOrdEqual             :Result:=Op.OpFOrdEqual             ;
  Op.OpFOrdLessThanEqual     :Result:=Op.OpFOrdGreaterThanEqual  ;
  Op.OpFOrdGreaterThan       :Result:=Op.OpFOrdLessThan          ;
  Op.OpFOrdNotEqual          :Result:=Op.OpFOrdNotEqual          ;
  Op.OpFOrdGreaterThanEqual  :Result:=Op.OpFOrdLessThanEqual     ;
  Op.OpOrdered               :Result:=Op.OpOrdered               ;
  Op.OpUnordered             :Result:=Op.OpUnordered             ;
  Op.OpFUnordLessThan        :Result:=Op.OpFUnordGreaterThan     ;
  Op.OpFUnordEqual           :Result:=Op.OpFUnordEqual           ;
  Op.OpFUnordLessThanEqual   :Result:=Op.OpFUnordGreaterThanEqual;
  Op.OpFUnordGreaterThan     :Result:=Op.OpFUnordLessThan        ;
  Op.OpFUnordNotEqual        :Result:=Op.OpFUnordNotEqual        ;
  Op.OpFUnordGreaterThanEqual:Result:=Op.OpFUnordLessThanEqual   ;

  Op.OpIEqual                :Result:=Op.OpIEqual                ;
  Op.OpINotEqual             :Result:=Op.OpINotEqual             ;

  Op.OpSLessThan             :Result:=Op.OpSGreaterThan          ;
  Op.OpSLessThanEqual        :Result:=Op.OpSGreaterThanEqual     ;
  Op.OpSGreaterThan          :Result:=Op.OpSLessThan             ;
  Op.OpSGreaterThanEqual     :Result:=Op.OpSLessThanEqual        ;

  Op.OpULessThan             :Result:=Op.OpUGreaterThan          ;
  Op.OpULessThanEqual        :Result:=Op.OpUGreaterThanEqual     ;
  Op.OpUGreaterThan          :Result:=Op.OpULessThan             ;
  Op.OpUGreaterThanEqual     :Result:=Op.OpULessThanEqual        ;
  else;
 end;
end;

Function get_inverse_not_cmp_op(OpId:DWORD):DWORD;
begin
 Result:=0;
 Case OpId of
  Op.OpFOrdLessThan          :Result:=Op.OpFUnordGreaterThanEqual;
  Op.OpFOrdEqual             :Result:=Op.OpFUnordNotEqual;
  Op.OpFOrdLessThanEqual     :Result:=Op.OpFUnordGreaterThan;
  Op.OpFOrdGreaterThan       :Result:=Op.OpFUnordLessThanEqual;
  Op.OpFOrdNotEqual          :Result:=Op.OpFUnordEqual;
  Op.OpFOrdGreaterThanEqual  :Result:=Op.OpFUnordLessThan;
  Op.OpOrdered               :Result:=Op.OpUnordered;
  Op.OpUnordered             :Result:=Op.OpOrdered;
  Op.OpFUnordLessThan        :Result:=Op.OpFOrdGreaterThanEqual;
  Op.OpFUnordEqual           :Result:=Op.OpFOrdNotEqual;
  Op.OpFUnordLessThanEqual   :Result:=Op.OpFOrdGreaterThan;
  Op.OpFUnordGreaterThan     :Result:=Op.OpFOrdLessThanEqual;
  Op.OpFUnordNotEqual        :Result:=Op.OpFOrdEqual;
  Op.OpFUnordGreaterThanEqual:Result:=Op.OpFOrdLessThan;

  Op.OpIEqual                :Result:=Op.OpINotEqual;
  Op.OpINotEqual             :Result:=Op.OpIEqual;

  Op.OpSLessThan             :Result:=Op.OpSGreaterThanEqual;
  Op.OpSLessThanEqual        :Result:=Op.OpSGreaterThan;
  Op.OpSGreaterThan          :Result:=Op.OpSLessThanEqual;
  Op.OpSGreaterThanEqual     :Result:=Op.OpSLessThan;

  Op.OpULessThan             :Result:=Op.OpUGreaterThanEqual;
  Op.OpULessThanEqual        :Result:=Op.OpUGreaterThan;
  Op.OpUGreaterThan          :Result:=Op.OpULessThanEqual;
  Op.OpUGreaterThanEqual     :Result:=Op.OpULessThan;
  else;
 end;
end;

Function is_term_op(OpId:DWORD):Boolean;
begin
 Case OpId of
  Op.OpBranch,
  Op.OpBranchConditional,
  Op.OpSwitch,
  Op.OpReturn,
  Op.OpReturnValue,
  Op.OpKill,
  Op.OpTerminateInvocation,
  Op.OpDemoteToHelperInvocation,
  Op.OpUnreachable:Result:=True;
  else
   Result:=False;
 end;
end;

Function is_merge_op(OpId:DWORD):Boolean;
begin
 Case OpId of
  Op.OpSelectionMerge,
  Op.OpLoopMerge:Result:=True;
  else
   Result:=False;
 end;
end;

Function is_term_op(pLine:TSpirvOp):Boolean;
begin
 Result:=False;
 if (pLine=nil) then Exit;

 repeat //up

  if pLine.IsType(ntOpBlock) then
  begin
   //
  end else
  if pLine.IsType(ntOp) then
  begin
   if not pLine.is_cleared then
   begin
    Case pLine.OpId of
     Op.OpNop :; //
     Op.OpLine:; //
     else
      Break;
    end;
   end;
  end else
  begin
   Exit;
  end;

  pLine:=flow_down_prev_up(pLine);

 until false;

 Result:=is_term_op(pLine.OpId);
end;

Function get_next_term_op(pLine:TSpirvOp):TSpirvOp;
begin
 Result:=nil;
 if (pLine=nil) then Exit;

 repeat //up
  pLine:=flow_down_next_up(pLine);

  if pLine.IsType(ntOpBlock) then
  begin
   //
  end else
  if pLine.IsType(ntOp) then
  begin
   if not pLine.is_cleared then
   begin
    Case pLine.OpId of
     Op.OpNop :; //
     Op.OpLine:; //
     else
      Break;
    end;
   end;
  end else
  begin
   Exit;
  end;

 until false;

 if is_term_op(pLine.OpId) then
 begin
  Result:=pLine;
 end;
end;

procedure up_merge_line(var pLine:TSpirvOp);
var
 node:TSpirvOp;
begin
 repeat
  if pLine.IsType(ntOp) then
  begin
   if pLine.is_cleared or is_merge_op(pLine.OpId) then
   begin
    node:=flow_prev_up(pLine);
    if (node<>nil) then
    begin
     pLine:=node;
     Continue;
    end;
   end;
  end;
  Break;
 until false;
end;

function CompareParam(p1,p2:POpParamNode):Boolean;
begin
 Result:=(p1.Value=p2.Value);
end;

function CompareOp(p1,p2:TSpirvOp):Boolean;
var
 n1,n2:POpParamNode;
begin
 Result:=False;

 if not p1.IsType(ntOp) then Exit;
 if not p2.IsType(ntOp) then Exit;

 if (p1.OpId<>p2.OpId) then Exit;
 if (p1.pDst<>p2.pDst) then Exit;

 n1:=p1.ParamNode(0);
 n2:=p2.ParamNode(0);

 While (n1<>nil) do
 begin
  if (n2=nil) then Exit;
  if not CompareParam(n1,n2) then Exit;
  n1:=n1.Next;
  n2:=n2.Next;
 end;

 Result:=(n2=nil);
end;

function FindUpSameOp(pLine,node:TSpirvOp):TSpirvOp;
begin
 Result:=nil;
 if (pLine=nil) or (node=nil) then Exit;

 While (pLine<>nil) do
 begin
  if not pLine.is_cleared then
  begin
   if CompareOp(pLine,node) then Exit(pLine);
  end;
  pLine:=flow_prev_up(pLine);
 end;
end;

function IsDominUp(pNodeUp,pLine:TSpirvOp):Boolean;
begin
 Result:=False;
 if (pNodeUp=nil) or (pLine=nil) then Exit;

 While (pLine<>nil) do
 begin
  if (pNodeUp=pLine) then Exit(True);
  pLine:=flow_prev_up(pLine);
 end;
end;

type
 a_volatile_node=array of TsrRegNode;

procedure add_node(var A:a_volatile_node;node:TsrRegNode);
var
 i:Integer;
begin
 //check exist
 if Length(A)<>0 then
 For i:=0 to High(A) do
 begin
  if (A[i]=node) then Exit;
 end;
 //
 Insert([node],A,High(A));
end;

procedure add_volatile(var A:a_volatile_node;V:TsrVolatile);
var
 node:TStoreNode;
begin
 node:=V.FList.pHead;
 while (node<>nil) do
 begin
  add_node(A,RegDown(node.src));
  //
  node:=node.pNext;
 end;
end;

function next_volatile(var A:a_volatile_node;var i:Integer):TsrRegNode;
begin
 if (i<Length(A)) then
 begin
  Result:=A[i];
  Inc(i);
 end else
 begin
  Result:=nil;
 end;
end;

type
 AsrChain=array of TsrChain;

function GetChainRegNode2(node:TsrRegNode):AsrChain;
var
 pOp:TSpirvOp;
 V:TsrVolatile;
 C:TsrChain;
 A:a_volatile_node;
 i:Integer;
begin
 Result:=[];
 A:=[];
 i:=0;

 while (node<>nil) do
 begin
  node:=RegDown(node);

  if node.pWriter.IsType(TsrVolatile) then
  begin
   V:=node.pWriter.specialize AsType<TsrVolatile>;
   add_volatile(A,V);
   node:=next_volatile(A,i);
  end;

  pOp:=node.pWriter.specialize AsType<ntOp>;
  if (pOp<>nil) then
  if (pOp.OpId=Op.OpLoad) then
  begin
   C:=pOp.ParamNode(0).Value.specialize AsType<ntChain>;

   if (C<>nil) then
   begin
    Insert([C],Result,High(Result));
   end;
  end;

  node:=next_volatile(A,i);
 end;
end;

function GetChainRegNode(node:TsrRegNode):TsrChain;
Var
 A:AsrChain;
begin
 A:=GetChainRegNode2(node);
 if (Length(A)=0) then Exit(nil);
 if (Length(A)>1) then
 begin
  Assert(false,'Multiple reachable chains are not supported!');
 end;
 Result:=A[0];
end;

function GetSourceRegNode(node:TsrRegNode):TsrNode;
var
 pOp:TSpirvOp;
 pVar:TsrVariable;
begin
 Result:=nil;
 node:=RegDown(node);
 pOp:=node.pWriter.specialize AsType<ntOp>;
 if (pOp=nil) then Exit;
 if (pOp.OpId<>Op.OpLoad) then Exit;
 pVar:=pOp.ParamNode(0).Value.specialize AsType<ntVariable>;
 if (pVar=nil) then Exit;
 Result:=pVar.pSource;
end;

function RegDownTestError(node:TsrRegNode):TsrRegNode;
var
 tmp:TsrRegNode;
begin
 //backtrace
 Result:=nil;
 tmp:=node;
 while (tmp<>nil) do
 begin
  if GetGlobalIndex(tmp.pLine)>GetGlobalIndex(node.pLine) then
  begin
   Exit(tmp);
  end;
  tmp:=tmp.AsReg; //next
 end;
end;

end.

