unit srOpUtils;

{$mode objfpc}{$H+}

interface

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

const
 OpIAddExt=DWORD(-1);
 OpISubExt=DWORD(-2);
 OpAbsDiff=DWORD(-3);
 OpWQM32  =DWORD(-4);

 OpBFEU32 =DWORD(-5);
 OpBFIB32 =DWORD(-6);

 OpPackAnc=DWORD(-7);
 OpPackOfs=DWORD(-8);
 OpMakeExp=DWORD(-9);
 OpMakeVec=DWORD(-10);
 OpMakeCub=DWORD(-11);

 OpCUBEID =DWORD(-12);
 OpCUBESC =DWORD(-13);
 OpCUBETC =DWORD(-14);
 OpCUBEMA =DWORD(-15);

function  InsSpirvOp(pLine,pNew:PspirvOp):PspirvOp;
Function  get_inverse_cmp_op(OpId:DWORD):DWORD;
Function  is_term_op(OpId:DWORD):Boolean;
Function  is_merge_op(OpId:DWORD):Boolean;
Function  is_term_op(pLine:PspirvOp):Boolean;
procedure up_merge_line(var pLine:PspirvOp);
function  FindUpSameOp(pLine,node:PspirvOp):PspirvOp;
function  IsDominUp(pNodeUp,pLine:PspirvOp):Boolean;

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

function  GetChainRegNode(node:PsrRegNode):PsrChain;
function  GetSourceRegNode(node:PsrRegNode):PsrNode;

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

implementation

//--

function flow_down_next_up(pLine:PspirvOp):PspirvOp;
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:PspirvOp):PspirvOp;
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:PspirvOp):PspirvOp;

 function _last(p:PspirvOp):PspirvOp;
 begin
  Result:=nil;
  if (p<>nil) then
  if p^.IsType(ntOpBlock) then
  if not (PsrOpBlock(p)^.Block.bType in [btCond,btLoop]) 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:PspirvOp):PspirvOp;
var
 tmp:PspirvOp;
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:PspirvOp):DWORD;
var
 pParent:PsrOpBlock;
 node:PspirvOp;
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:PspirvOp):DWORD;
var
 pParent:PsrOpBlock;
 node:PspirvOp;
begin
 Result:=0;
 if (pLine=nil) then Exit;
 node:=pLine;
 pParent:=node^.Parent;
 While (pParent<>nil) do
 begin
  Result:=Result+GetCurrentIndex(node);
  node:=PspirvOp(pParent);
  pParent:=node^.Parent;
 end;
end;

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

function isGTLine(p1,p2:PspirvOp):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:PspirvOp):Boolean; //(p1>p2)
var
 pParent1:PsrOpBlock;
 pParent2:PsrOpBlock;
begin
 Result:=False;
 pParent1:=p1^.Parent;
 pParent2:=p2^.Parent;
 Result:=(pParent1^.Level>pParent2^.Level);
end;

function MaxLine(p1,p2:PspirvOp):PspirvOp;
var
 pParent:PsrOpBlock;
 node:PspirvOp;
 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:=PspirvOp(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:PspirvOp):PspirvOp;
begin
 if (MaxLine(p1,p2)=p1) then
 begin
  Result:=p2;
 end else
 begin
  Result:=p1;
 end;
end;

Function IsParentOf(pLine,pCurr:PsrOpBlock):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:PSpirvOp):Boolean;
begin
 Result:=IsParentOf(pLine^.Parent,pCurr^.Parent);
end;

function GetMaxPlace(pLine:PSpirvOp;count:Byte;src:PPsrRegNode):PSpirvOp;
var
 m:PSpirvOp;
 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
  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_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.OpSLessThan             :Result:=Op.OpSGreaterThan          ;
  Op.OpIEqual                :Result:=Op.OpIEqual                ;
  Op.OpSLessThanEqual        :Result:=Op.OpSGreaterThanEqual     ;
  Op.OpSGreaterThan          :Result:=Op.OpSLessThan             ;
  Op.OpINotEqual             :Result:=Op.OpINotEqual             ;
  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
   Assert(false);
 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:PspirvOp):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:; //
     else
      Break;
    end;
   end;
  end else
  begin
   Exit;
  end;

  pLine:=flow_down_prev_up(pLine);

 until false;

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

procedure up_merge_line(var pLine:PspirvOp);
var
 node:PspirvOp;
begin
 repeat
  if pLine^.IsType(ntOp) then
  begin
   if pLine^.is_cleared or is_merge_op(pLine^.OpId) then
   begin
    node:=pLine^.Prev;
    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:PspirvOp):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:PspirvOp):PspirvOp;
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:PspirvOp):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;

function GetChainRegNode(node:PsrRegNode):PsrChain;
var
 pOp:PSpirvOp;
begin
 Result:=nil;
 node:=RegDown(node);
 pOp:=node^.pWriter^.AsType(ntOp);
 if (pOp=nil) then Exit;
 if (pOp^.OpId<>Op.OpLoad) then Exit;
 Result:=pOp^.ParamNode(0)^.Value^.AsType(ntChain);
end;

function GetSourceRegNode(node:PsrRegNode):PsrNode;
var
 pOp:PSpirvOp;
 pVar:PsrVariable;
begin
 Result:=nil;
 node:=RegDown(node);
 pOp:=node^.pWriter^.AsType(ntOp);
 if (pOp=nil) then Exit;
 if (pOp^.OpId<>Op.OpLoad) then Exit;
 pVar:=pOp^.ParamNode(0)^.Value^.AsType(ntVariable);
 if (pVar=nil) then Exit;
 Result:=pVar^.pSource;
end;

end.

