opttail.pas 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. {
  2. Tail recursion optimization
  3. Copyright (c) 2006 by Florian Klaempfl
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15. ****************************************************************************
  16. }
  17. unit opttail;
  18. {$i fpcdefs.inc}
  19. interface
  20. uses
  21. symdef,node;
  22. procedure do_opttail(var n : tnode;p : tprocdef);
  23. implementation
  24. uses
  25. globtype,
  26. symconst,symsym,
  27. defcmp,defutil,
  28. nutils,nbas,nflw,ncal,nld,ncnv,nmem,
  29. pass_1,
  30. paramgr;
  31. procedure do_opttail(var n : tnode;p : tprocdef);
  32. var
  33. labelnode : tlabelnode;
  34. function find_and_replace_tailcalls(var n : tnode) : boolean;
  35. var
  36. usedcallnode : tcallnode;
  37. function has_copyback_paras(call: tcallnode): boolean;
  38. var
  39. n: tcallparanode;
  40. begin
  41. n:=tcallparanode(call.left);
  42. result:=false;
  43. while assigned(n) do
  44. begin
  45. if assigned(n.fparacopyback) then
  46. begin
  47. result:=true;
  48. exit;
  49. end;
  50. n:=tcallparanode(n.right);
  51. end;
  52. end;
  53. function is_optimizable_recursivecall(n : tnode) : boolean;
  54. begin
  55. result:=
  56. (n.nodetype=calln) and
  57. (tcallnode(n).procdefinition=p) and
  58. not(assigned(tcallnode(n).methodpointer)) and
  59. not has_copyback_paras(tcallnode(n));
  60. if result then
  61. usedcallnode:=tcallnode(n)
  62. else
  63. { obsolete type cast? }
  64. result:=((n.nodetype=typeconvn) and (ttypeconvnode(n).convtype=tc_equal) and is_optimizable_recursivecall(ttypeconvnode(n).left));
  65. end;
  66. function is_resultassignment(n : tnode) : boolean;
  67. begin
  68. result:=((n.nodetype=loadn) and (tloadnode(n).symtableentry=p.funcretsym)) or
  69. ((n.nodetype=typeconvn) and (ttypeconvnode(n).convtype=tc_equal) and is_resultassignment(ttypeconvnode(n).left));
  70. end;
  71. var
  72. calcnodes,
  73. copynodes,
  74. hp : tnode;
  75. nodes,
  76. calcstatements,
  77. copystatements : tstatementnode;
  78. paranode : tcallparanode;
  79. tempnode : ttempcreatenode;
  80. loadnode : tloadnode;
  81. oldnodetree : tnode;
  82. useaddr : boolean;
  83. begin
  84. { no tail call found and replaced so far }
  85. result:=false;
  86. if n=nil then
  87. exit;
  88. usedcallnode:=nil;
  89. case n.nodetype of
  90. statementn:
  91. begin
  92. hp:=n;
  93. { search last node }
  94. while assigned(tstatementnode(hp).right) do
  95. hp:=tstatementnode(hp).right;
  96. result:=find_and_replace_tailcalls(tstatementnode(hp).left);
  97. end;
  98. ifn:
  99. begin
  100. result:=find_and_replace_tailcalls(tifnode(n).right);
  101. { avoid short bool eval here }
  102. result:=find_and_replace_tailcalls(tifnode(n).t1) or result;
  103. end;
  104. calln,
  105. assignn:
  106. begin
  107. if ((n.nodetype=calln) and is_optimizable_recursivecall(n)) or
  108. ((n.nodetype=assignn) and is_resultassignment(tbinarynode(n).left) and
  109. is_optimizable_recursivecall(tbinarynode(n).right)) then
  110. begin
  111. { found one! }
  112. {
  113. writeln('tail recursion optimization for ',p.mangledname);
  114. printnode(output,n);
  115. }
  116. { create assignments for all parameters }
  117. { this is hairy to do because one parameter could be used to calculate another one, so
  118. assign them first to temps and then add them }
  119. calcnodes:=internalstatements(calcstatements);
  120. copynodes:=internalstatements(copystatements);
  121. paranode:=tcallparanode(usedcallnode.left);
  122. while assigned(paranode) do
  123. begin
  124. if assigned(paranode.fparainit) then
  125. begin
  126. addstatement(calcstatements,paranode.fparainit);
  127. paranode.fparainit:=nil;
  128. end;
  129. useaddr:=(paranode.parasym.varspez in [vs_var,vs_constref]) or
  130. ((paranode.parasym.varspez=vs_const) and
  131. paramanager.push_addr_param(paranode.parasym.varspez,paranode.parasym.vardef,p.proccalloption)) or
  132. ((paranode.parasym.varspez=vs_value) and
  133. is_open_array(paranode.parasym.vardef));
  134. if useaddr then
  135. begin
  136. tempnode:=ctempcreatenode.create(voidpointertype,voidpointertype.size,tt_persistent,true);
  137. addstatement(calcstatements,tempnode);
  138. addstatement(calcstatements,
  139. cassignmentnode.create(
  140. ctemprefnode.create(tempnode),
  141. caddrnode.create_internal(paranode.left)
  142. ));
  143. end
  144. else
  145. begin
  146. tempnode:=ctempcreatenode.create(paranode.left.resultdef,paranode.left.resultdef.size,tt_persistent,true);
  147. addstatement(calcstatements,tempnode);
  148. addstatement(calcstatements,
  149. cassignmentnode.create_internal(
  150. ctemprefnode.create(tempnode),
  151. paranode.left
  152. ));
  153. end;
  154. { "cast" away const varspezs }
  155. loadnode:=cloadnode.create(paranode.parasym,paranode.parasym.owner);
  156. include(tloadnode(loadnode).loadnodeflags,loadnf_isinternal_ignoreconst);
  157. { load the address of the symbol instead of symbol }
  158. if useaddr then
  159. include(tloadnode(loadnode).loadnodeflags,loadnf_load_addr);
  160. addstatement(copystatements,
  161. cassignmentnode.create_internal(
  162. loadnode,
  163. ctemprefnode.create(tempnode)
  164. ));
  165. addstatement(copystatements,ctempdeletenode.create_normal_temp(tempnode));
  166. { reused }
  167. paranode.left:=nil;
  168. paranode:=tcallparanode(paranode.right);
  169. end;
  170. oldnodetree:=n;
  171. n:=internalstatements(nodes);
  172. if assigned(usedcallnode.callinitblock) then
  173. begin
  174. addstatement(nodes,usedcallnode.callinitblock);
  175. usedcallnode.callinitblock:=nil;
  176. end;
  177. addstatement(nodes,calcnodes);
  178. addstatement(nodes,copynodes);
  179. { create goto }
  180. addstatement(nodes,cgotonode.create(labelnode.labsym));
  181. if assigned(usedcallnode.callcleanupblock) then
  182. begin
  183. { callcleanupblock should contain only temp. node clean up }
  184. checktreenodetypes(usedcallnode.callcleanupblock,
  185. [tempdeleten,blockn,statementn,temprefn,nothingn]);
  186. addstatement(nodes,usedcallnode.callcleanupblock);
  187. usedcallnode.callcleanupblock:=nil;
  188. end;
  189. oldnodetree.free;
  190. do_firstpass(n);
  191. result:=true;
  192. end;
  193. end;
  194. blockn:
  195. result:=find_and_replace_tailcalls(tblocknode(n).left);
  196. else
  197. ;
  198. end;
  199. end;
  200. var
  201. s : tstatementnode;
  202. oldnodes : tnode;
  203. i : longint;
  204. labelsym : tlabelsym;
  205. begin
  206. { check if the parameters actually would support tail recursion elimination }
  207. for i:=0 to p.paras.count-1 do
  208. with tparavarsym(p.paras[i]) do
  209. if (varspez=vs_out) or
  210. { parameters requiring tables are too complicated to handle
  211. and slow down things anyways so a tail recursion call
  212. makes no sense
  213. }
  214. is_managed_type(vardef) then
  215. exit;
  216. labelsym:=clabelsym.create('$opttail');
  217. labelnode:=clabelnode.create(cnothingnode.create,labelsym);
  218. if find_and_replace_tailcalls(n) then
  219. begin
  220. oldnodes:=n;
  221. n:=internalstatements(s);
  222. addstatement(s,labelnode);
  223. addstatement(s,oldnodes);
  224. end
  225. else
  226. labelnode.free;
  227. end;
  228. end.