{ Common subexpression elimination on base blocks Copyright (c) 2005-2012 by Florian Klaempfl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. **************************************************************************** } unit optcse; {$i fpcdefs.inc} { $define csedebug} { $define csestats} interface uses node; { the function creates non optimal code so far: - call para nodes are cse barriers because they can be reordered and thus the temp. creation could be done too late - the cse knows nothing about register pressure. In case of high register pressure, cse might have a negative impact - the list of cseinvariant node types and inline numbers is not complete yet Further, it could be done probably in a faster way though the complexity can't probably not reduced } function do_optcse(var rootnode : tnode) : tnode; implementation uses globtype,globals, cutils,cclasses, nutils,compinnr, nbas,nld,ninl,ncal,nadd,nmem, pass_1, symconst,symdef,symsym, defutil, optbase; const cseinvariant : set of tnodetype = [addn,muln,subn,divn,slashn,modn,andn,orn,xorn,notn,vecn, derefn,equaln,unequaln,ltn,gtn,lten,gten,typeconvn,subscriptn, inn,symdifn,shrn,shln,ordconstn,realconstn,unaryminusn,pointerconstn,stringconstn,setconstn,niln, setelementn,{arrayconstructorn,arrayconstructorrangen,} isn,asn,starstarn,nothingn,temprefn,loadparentfpn {,callparan},assignn,addrn]; function searchsubdomain(var n:tnode; arg: pointer) : foreachnoderesult; begin if (n.nodetype in cseinvariant) or ((n.nodetype=inlinen) and (tinlinenode(n).inlinenumber in [in_length_x,in_assigned_x,in_sqr_real,in_sqrt_real,in_sin_real,in_cos_real,in_abs_long, in_abs_real,in_exp_real,in_ln_real,in_pi_real,in_popcnt_x,in_arctan_real,in_round_real,in_trunc_real, { cse on fma will still not work because it would require proper handling of call nodes with more than one parameter } in_fma_single,in_fma_double,in_fma_extended,in_fma_float128]) ) or ((n.nodetype=callparan) and not(assigned(tcallparanode(n).right))) or ((n.nodetype=loadn) and not((tloadnode(n).symtableentry.typ in [staticvarsym,localvarsym,paravarsym]) and (vo_volatile in tabstractvarsym(tloadnode(n).symtableentry).varoptions)) ) then result:=fen_true else begin pboolean(arg)^:=false; result:=fen_norecurse_true; end; end; type tlists = record nodelist : tfplist; locationlist : tfplist; equalto : tfplist; refs : tfplist; avail : TDFASet; end; plists = ^tlists; { collectnodes needs the address of itself to call foreachnodestatic, so we need a wrapper because @ inside 1) ) and { Const nodes however are only considered if their complexity is >1 This might be the case for the risc architectures if they need more than one instruction to load this particular value } (not(is_constnode(n)) or (node_complexity(n)>1))) {$if not(defined(i386)) and not(defined(i8086))} or { store reference of expression? } { loading the address of a global symbol takes typically more than one instruction on every platform except i8086/i386 so consider in this case loading the address of the data } (((n.resultdef.typ in [arraydef,recorddef]) or is_object(n.resultdef)) and not(is_dynamic_array(n.resultdef)) and (n.nodetype=loadn) and (tloadnode(n).symtableentry.typ=staticvarsym) ) {$endif not(defined(i386)) and not(defined(i8086))} ) then begin plists(arg)^.nodelist.Add(n); plists(arg)^.locationlist.Add(@n); plists(arg)^.refs.Add(nil); plists(arg)^.equalto.Add(pointer(-1)); DFASetInclude(plists(arg)^.avail,plists(arg)^.nodelist.count-1); for i:=0 to plists(arg)^.nodelist.count-2 do begin if tnode(plists(arg)^.nodelist[i]).isequal(n) and DFASetIn(plists(arg)^.avail,i) then begin { use always the first occurence } if plists(arg)^.equalto[i]<>pointer(-1) then plists(arg)^.equalto[plists(arg)^.nodelist.count-1]:=plists(arg)^.equalto[i] else plists(arg)^.equalto[plists(arg)^.nodelist.count-1]:=pointer(ptrint(i)); plists(arg)^.refs[i]:=pointer(plists(arg)^.refs[i])+1; { tree has been found, no need to search further, sub-trees have been added by the first occurence of the tree already } result:=fen_norecurse_false; break; end; end; end; { boolean and/or require a special handling: after evaluating the and/or node, the expressions of the right side might not be available due to short boolean evaluation, so after handling the right side, mark those expressions as unavailable } if (n.nodetype in [orn,andn]) and is_boolean(taddnode(n).left.resultdef) then begin firstleftend:=high(longint); recurseintobooleanchain(n.nodetype,n); for i:=firstleftend to plists(arg)^.nodelist.count-1 do DFASetExclude(plists(arg)^.avail,i); result:=fen_norecurse_false; end; {$ifdef cpuhighleveltarget} { The high level targets use the functionality from ncgnstld for nested accesses, and that one stores the complete location of the nested variable in tloadnode.left rather than only the location of the parent context containing it. This causes problems with the CSE in case the nested variable is used as an lvalue, so disable CSE in that case } if (n.nodetype=loadn) and assigned(tloadnode(n).left) then result:=fen_norecurse_false; {$endif} end; function searchcsedomain(var n: tnode; arg: pointer) : foreachnoderesult; var csedomain : boolean; lists : tlists; templist : tfplist; i : longint; def : tstoreddef; nodes : tblocknode; creates, statements : tstatementnode; deletetemp : ttempdeletenode; hp : ttempcreatenode; addrstored : boolean; hp2 : tnode; begin result:=fen_false; nodes:=nil; if n.nodetype in cseinvariant then begin csedomain:=true; foreachnodestatic(pm_postprocess,n,@searchsubdomain,@csedomain); if not(csedomain) then begin { try to transform the tree to get better cse domains, consider: + (1) / \ (2) + C / \ A B if A is not cse'able but B and C are, then the compiler cannot do cse so the tree is transformed into + / \ A + / \ B C Because A could be another tree of this kind, the whole process is done in a while loop } if (n.nodetype in [andn,orn,addn,muln]) and (n.nodetype=tbinarynode(n).left.nodetype) and { do is optimizations only for integers, reals (no currency!), vectors, sets or booleans } (is_integer(n.resultdef) or is_real(n.resultdef) or is_vector(n.resultdef) or is_set(n.resultdef) or is_boolean(n.resultdef)) and { either if fastmath is on } ((cs_opt_fastmath in current_settings.optimizerswitches) or { or for the logical operators, they cannot overflow } (n.nodetype in [andn,orn]) or { or for integers if range checking is off } ((is_integer(n.resultdef) and (n.localswitches*[cs_check_range,cs_check_overflow]=[]) and (tbinarynode(n).left.localswitches*[cs_check_range,cs_check_overflow]=[]))) or { for sets, we can do this always } (is_set(n.resultdef)) ) then while (n.nodetype=tbinarynode(n).left.nodetype) and { if node (1) is fully boolean evaluated and node (2) not, we cannot do the swap as this might result in B being evaluated always, the other way round is no problem, C is still evaluated only if needed } (not(is_boolean(n.resultdef)) or not(n.nodetype in [andn,orn]) or doshortbooleval(n) or not(doshortbooleval(tbinarynode(n).left))) and { the resulttypes of the operands we'll swap must be equal, required in case of a 32x32->64 multiplication, then we cannot swap out one of the 32 bit operands for a 64 bit one } (tbinarynode(tbinarynode(n).left).left.resultdef=tbinarynode(n).left.resultdef) and (tbinarynode(n).left.resultdef=tbinarynode(n).right.resultdef) do begin csedomain:=true; foreachnodestatic(pm_postprocess,tbinarynode(n).right,@searchsubdomain,@csedomain); if csedomain then begin csedomain:=true; foreachnodestatic(pm_postprocess,tbinarynode(tbinarynode(n).left).right,@searchsubdomain,@csedomain); if csedomain then begin { move the full boolean evaluation of (2) to (1), if it was there (so it again applies to A and what follows) } if not(doshortbooleval(tbinarynode(n).left)) and doshortbooleval(n) then begin n.localswitches:=n.localswitches+(tbinarynode(n).left.localswitches*[cs_full_boolean_eval]); exclude(tbinarynode(n).left.localswitches,cs_full_boolean_eval); tbinarynode(n).left.flags:=tbinarynode(n).left.flags+(n.flags*[nf_short_bool]); exclude(n.Flags,nf_short_bool); end; hp2:=tbinarynode(tbinarynode(n).left).left; tbinarynode(tbinarynode(n).left).left:=tbinarynode(tbinarynode(n).left).right; tbinarynode(tbinarynode(n).left).right:=tbinarynode(n).right; tbinarynode(n).right:=tbinarynode(n).left; tbinarynode(n).left:=hp2; { the transformed tree could result in new possibilities to fold constants so force a firstpass on the root node } exclude(tbinarynode(n).right.flags,nf_pass1_done); do_firstpass(tbinarynode(n).right); end else break; end else break; end; end else begin statements:=nil; result:=fen_norecurse_true; {$ifdef csedebug} writeln('============ cse domain =================='); printnode(output,n); writeln('Complexity: ',node_complexity(n)); {$endif csedebug} lists.nodelist:=tfplist.create; lists.locationlist:=tfplist.create; lists.equalto:=tfplist.create; lists.refs:=tfplist.create; foreachnodestatic(pm_postprocess,n,@collectnodes,@lists); templist:=tfplist.create; templist.count:=lists.nodelist.count; { check all nodes if one is used more than once } for i:=0 to lists.nodelist.count-1 do begin { current node used more than once? } if assigned(lists.refs[i]) then begin if not(assigned(statements)) then begin nodes:=internalstatements(statements); addstatement(statements,internalstatements(creates)); end; def:=tstoreddef(tnode(lists.nodelist[i]).resultdef); { we cannot handle register stored records or array in CSE yet but we can store their reference } addrstored:=((def.typ in [arraydef,recorddef]) or is_object(def)) and not(is_dynamic_array(def)); if addrstored then templist[i]:=ctempcreatenode.create_value(cpointerdef.getreusable(def),voidpointertype.size,tt_persistent, true,caddrnode.create_internal(tnode(lists.nodelist[i]))) else templist[i]:=ctempcreatenode.create_value(def,def.size,tt_persistent, def.is_intregable or def.is_fpuregable or def.is_const_intregable,tnode(lists.nodelist[i])); { the value described by the temp. is immutable and the temp. can be always in register ttempcreatenode.create normally takes care of the register location but it does not know about immutability so it cannot take care of managed types } ttempcreatenode(templist[i]).includetempflag(ti_const); ttempcreatenode(templist[i]).includetempflag(ti_may_be_in_reg); { make debugging easier and set temp. location to the original location } tnode(templist[i]).fileinfo:=tnode(lists.nodelist[i]).fileinfo; addstatement(creates,tnode(templist[i])); { the delete node has no semantic use yet, it is just used to clean up memory } deletetemp:=ctempdeletenode.create(ttempcreatenode(templist[i])); deletetemp.includetempflag(ti_cleanup_only); addstatement(tstatementnode(arg^),deletetemp); { make debugging easier and set temp. location to the original location } creates.fileinfo:=tnode(lists.nodelist[i]).fileinfo; hp:=ttempcreatenode(templist[i]); do_firstpass(tnode(hp)); templist[i]:=hp; if addrstored then pnode(lists.locationlist[i])^:=cderefnode.Create(ctemprefnode.create(ttempcreatenode(templist[i]))) else pnode(lists.locationlist[i])^:=ctemprefnode.create(ttempcreatenode(templist[i])); { make debugging easier and set temp. location to the original location } pnode(lists.locationlist[i])^.fileinfo:=tnode(lists.nodelist[i]).fileinfo; do_firstpass(pnode(lists.locationlist[i])^); {$ifdef csedebug} printnode(output,statements); {$endif csedebug} end { current node reference to another node? } else if lists.equalto[i]<>pointer(-1) then begin def:=tstoreddef(tnode(lists.nodelist[i]).resultdef); { we cannot handle register stored records or array in CSE yet but we can store their reference } addrstored:=((def.typ in [arraydef,recorddef]) or is_object(def)) and not(is_dynamic_array(def)); {$if defined(csedebug) or defined(csestats)} writeln; writeln('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); writeln('Complexity: ',node_complexity(tnode(lists.nodelist[i])),' Node ',i,' equals Node ',ptrint(lists.equalto[i])); printnode(output,tnode(lists.nodelist[i])); printnode(output,tnode(lists.nodelist[ptrint(lists.equalto[i])])); writeln('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); writeln; {$endif defined(csedebug) or defined(csestats)} templist[i]:=templist[ptrint(lists.equalto[i])]; if addrstored then pnode(lists.locationlist[i])^:=cderefnode.Create(ctemprefnode.create(ttempcreatenode(templist[ptrint(lists.equalto[i])]))) else pnode(lists.locationlist[i])^:=ctemprefnode.create(ttempcreatenode(templist[ptrint(lists.equalto[i])])); { make debugging easier and set temp. location to the original location } pnode(lists.locationlist[i])^.fileinfo:=tnode(lists.nodelist[i]).fileinfo; do_firstpass(pnode(lists.locationlist[i])^); end; end; { clean up unused trees } for i:=0 to lists.nodelist.count-1 do if lists.equalto[i]<>pointer(-1) then tnode(lists.nodelist[i]).free; {$ifdef csedebug} writeln('nodes: ',lists.nodelist.count); writeln('=========================================='); {$endif csedebug} lists.nodelist.free; lists.locationlist.free; lists.equalto.free; lists.refs.free; templist.free; if assigned(statements) then begin { call para nodes need a special handling because they can be only children nodes of call nodes so the initialization code is inserted below the call para node } if n.nodetype=callparan then begin addstatement(statements,tcallparanode(n).left); tcallparanode(n).left:=nodes; do_firstpass(tcallparanode(n).left); end else begin addstatement(statements,n); n:=nodes; do_firstpass(n); end; {$ifdef csedebug} printnode(output,nodes); {$endif csedebug} end; end end; end; function do_optcse(var rootnode : tnode) : tnode; var deletes, statements : tstatementnode; deleteblock, rootblock : tblocknode; begin {$ifdef csedebug} writeln('===================================================================================='); writeln('CSE optimization pass started'); writeln('===================================================================================='); printnode(rootnode); writeln('===================================================================================='); writeln; {$endif csedebug} deleteblock:=internalstatements(deletes); foreachnodestatic(pm_postprocess,rootnode,@searchcsedomain,@deletes); rootblock:=internalstatements(statements); addstatement(statements,rootnode); addstatement(statements,deleteblock); rootnode:=rootblock; do_firstpass(rootnode); {$ifdef csedebug} writeln('===================================================================================='); writeln('CSE optimization result'); writeln('===================================================================================='); printnode(rootnode); writeln('===================================================================================='); writeln; {$endif csedebug} result:=nil; end; end.