1 {
2     Copyright (c) 2011 by Free Pascal development team
3 
4     Generate Win64-specific exception handling code
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 
20  ****************************************************************************
21 }
22 unit nx64flw;
23 
24 {$i fpcdefs.inc}
25 
26 interface
27 
28   uses
29     node,nflw,ncgflw,psub;
30 
31   type
32     tx64raisenode=class(tcgraisenode)
pass_1null33       function pass_1 : tnode;override;
34     end;
35 
36     tx64onnode=class(tcgonnode)
37       procedure pass_generate_code;override;
38     end;
39 
40     tx64tryexceptnode=class(tcgtryexceptnode)
41       procedure pass_generate_code;override;
42     end;
43 
44     tx64tryfinallynode=class(tcgtryfinallynode)
45       finalizepi: tcgprocinfo;
46       constructor create(l,r:TNode);override;
47       constructor create_implicit(l,r:TNode);override;
dogetcopynull48       function dogetcopy : tnode;override;
simplifynull49       function simplify(forinline: boolean): tnode;override;
50       procedure pass_generate_code;override;
51     end;
52 
53 implementation
54 
55   uses
56     globtype,globals,verbose,systems,
57     nbas,ncal,nutils,
58     symconst,symsym,symdef,
59     cgbase,cgobj,cgutils,tgobj,
60     cpubase,htypechk,
61     pass_1,pass_2,
62     aasmbase,aasmtai,aasmdata,aasmcpu,
63     procinfo,cpupi,procdefutil;
64 
65   var
66     endexceptlabel: tasmlabel;
67 
68 
69 { tx64raisenode }
70 
tx64raisenode.pass_1null71 function tx64raisenode.pass_1 : tnode;
72   var
73     statements : tstatementnode;
74     raisenode : tcallnode;
75   begin
76     { difference from generic code is that address stack is not popped on reraise }
77     if (target_info.system<>system_x86_64_win64) or assigned(left) then
78       result:=inherited pass_1
79     else
80       begin
81         result:=internalstatements(statements);
82         raisenode:=ccallnode.createintern('fpc_reraise',nil);
83         include(raisenode.callnodeflags,cnf_call_never_returns);
84         addstatement(statements,raisenode);
85       end;
86 end;
87 
88 { tx64onnode }
89 
90 procedure tx64onnode.pass_generate_code;
91   var
92     exceptvarsym : tlocalvarsym;
93   begin
94     if (target_info.system<>system_x86_64_win64) then
95       begin
96         inherited pass_generate_code;
97         exit;
98       end;
99 
100     location_reset(location,LOC_VOID,OS_NO);
101 
102     { RTL will put exceptobject into RAX when jumping here }
103     cg.a_reg_alloc(current_asmdata.CurrAsmList,NR_FUNCTION_RESULT_REG);
104 
105     { Retrieve exception variable }
106     if assigned(excepTSymtable) then
107       exceptvarsym:=tlocalvarsym(excepTSymtable.SymList[0])
108     else
109       exceptvarsym:=nil;
110 
111     if assigned(exceptvarsym) then
112       begin
113         exceptvarsym.localloc.loc:=LOC_REFERENCE;
114         exceptvarsym.localloc.size:=OS_ADDR;
115         tg.GetLocal(current_asmdata.CurrAsmList,sizeof(pint),voidpointertype,exceptvarsym.localloc.reference);
116         cg.a_load_reg_ref(current_asmdata.CurrAsmList,OS_ADDR,OS_ADDR,NR_FUNCTION_RESULT_REG,exceptvarsym.localloc.reference);
117       end;
118     cg.a_reg_dealloc(current_asmdata.CurrAsmList,NR_FUNCTION_RESULT_REG);
119 
120     if assigned(right) then
121       secondpass(right);
122 
123     { deallocate exception symbol }
124     if assigned(exceptvarsym) then
125       begin
126         tg.UngetLocal(current_asmdata.CurrAsmList,exceptvarsym.localloc.reference);
127         exceptvarsym.localloc.loc:=LOC_INVALID;
128       end;
129     cg.g_call(current_asmdata.CurrAsmList,'FPC_DONEEXCEPTION');
130     cg.a_jmp_always(current_asmdata.CurrAsmList,endexceptlabel);
131   end;
132 
133 { tx64tryfinallynode }
134 
reset_regvarsnull135 function reset_regvars(var n: tnode; arg: pointer): foreachnoderesult;
136   begin
137     case n.nodetype of
138       temprefn:
139         make_not_regable(n,[]);
140       calln:
141         include(tprocinfo(arg).flags,pi_do_call);
142     end;
143     result:=fen_true;
144   end;
145 
copy_parasizenull146 function copy_parasize(var n: tnode; arg: pointer): foreachnoderesult;
147   begin
148     case n.nodetype of
149       calln:
150         tcgprocinfo(arg).allocate_push_parasize(tcallnode(n).pushed_parasize);
151     end;
152     result:=fen_true;
153   end;
154 
155 constructor tx64tryfinallynode.create(l, r: TNode);
156   begin
157     inherited create(l,r);
158     if (target_info.system=system_x86_64_win64) and
159       { Don't create child procedures for generic methods, their nested-like
160         behavior causes compilation errors because real nested procedures
161         aren't allowed for generics. Not creating them doesn't harm because
162         generic node tree is discarded without generating code. }
163        not (df_generic in current_procinfo.procdef.defoptions) then
164       begin
165         finalizepi:=tcgprocinfo(current_procinfo.create_for_outlining('$fin$',current_procinfo.procdef.struct,potype_exceptfilter,voidtype,r));
166         { the init/final code is messing with asm nodes, so inform the compiler about this }
167         include(finalizepi.flags,pi_has_assembler_block);
168         { Regvar optimization for symbols is suppressed when using exceptions, but
169           temps may be still placed into registers. This must be fixed. }
170         foreachnodestatic(r,@reset_regvars,finalizepi);
171       end;
172   end;
173 
174 
175 constructor tx64tryfinallynode.create_implicit(l, r: TNode);
176   begin
177     inherited create_implicit(l, r);
178     if (target_info.system=system_x86_64_win64) then
179       begin
180         if df_generic in current_procinfo.procdef.defoptions then
181           InternalError(2013012501);
182 
183         finalizepi:=tcgprocinfo(current_procinfo.create_for_outlining('$fin$',current_procinfo.procdef.struct,potype_exceptfilter,voidtype,r));
184         include(finalizepi.flags,pi_do_call);
185         { the init/final code is messing with asm nodes, so inform the compiler about this }
186         include(finalizepi.flags,pi_has_assembler_block);
187         finalizepi.allocate_push_parasize(32);
188       end;
189   end;
190 
191 
tx64tryfinallynode.dogetcopynull192 function tx64tryfinallynode.dogetcopy: tnode;
193   var
194     n: tx64tryfinallynode;
195   begin
196     n:=tx64tryfinallynode(inherited dogetcopy);
197     if target_info.system=system_x86_64_win64 then
198       begin
199         n.finalizepi:=tcgprocinfo(cprocinfo.create(finalizepi.parent));
200         n.finalizepi.force_nested;
201         n.finalizepi.procdef:=create_outline_procdef('$fin$',current_procinfo.procdef.struct,potype_exceptfilter,voidtype);
202         n.finalizepi.entrypos:=finalizepi.entrypos;
203         n.finalizepi.entryswitches:=finalizepi.entryswitches;
204         n.finalizepi.exitpos:=finalizepi.exitpos;
205         n.finalizepi.exitswitches:=finalizepi.exitswitches;
206         n.finalizepi.flags:=finalizepi.flags;
207         { node already transformed? }
208         if assigned(finalizepi.code) then
209           begin
210             n.finalizepi.code:=finalizepi.code.getcopy;
211             n.right:=ccallnode.create(nil,tprocsym(n.finalizepi.procdef.procsym),nil,nil,[],nil);
212           end;
213       end;
214     result:=n;
215   end;
216 
217 
tx64tryfinallynode.simplifynull218 function tx64tryfinallynode.simplify(forinline: boolean): tnode;
219   begin
220     result:=inherited simplify(forinline);
221     if (target_info.system<>system_x86_64_win64) then
222       exit;
223     if (result=nil) then
224       begin
225         { actually, this is not really the right place to do a node transformation like this }
226         if not(assigned(finalizepi.code)) then
227           begin
228             finalizepi.code:=right;
229             foreachnodestatic(right,@copy_parasize,finalizepi);
230             right:=ccallnode.create(nil,tprocsym(finalizepi.procdef.procsym),nil,nil,[],nil);
231             firstpass(right);
232             { For implicit frames, no actual code is available at this time,
233               it is added later in assembler form. So store the nested procinfo
234               for later use. }
235             if implicitframe then
236               begin
237                 current_procinfo.finalize_procinfo:=finalizepi;
238               end;
239             { don't leave dangling pointer }
240             tcgprocinfo(current_procinfo).final_asmnode:=nil;
241           end;
242       end;
243   end;
244 
245 
246 procedure emit_nop;
247   var
248     dummy: TAsmLabel;
249   begin
250     { To avoid optimizing away the whole thing, prepend a jumplabel with increased refcount }
251     current_asmdata.getjumplabel(dummy);
252     dummy.increfs;
253     cg.a_label(current_asmdata.CurrAsmList,dummy);
254     current_asmdata.CurrAsmList.concat(Taicpu.op_none(A_NOP,S_NO));
255   end;
256 
257 
258 procedure tx64tryfinallynode.pass_generate_code;
259   var
260     trylabel,
261     endtrylabel,
262     finallylabel,
263     endfinallylabel,
264     templabel,
265     oldexitlabel: tasmlabel;
266     oldflowcontrol: tflowcontrol;
267     catch_frame: boolean;
268   begin
269     if (target_info.system<>system_x86_64_win64) then
270       begin
271         inherited pass_generate_code;
272         exit;
273       end;
274 
275     location_reset(location,LOC_VOID,OS_NO);
276 
277     { Do not generate a frame that catches exceptions if the only action
278       would be reraising it. Doing so is extremely inefficient with SEH
279       (in contrast with setjmp/longjmp exception handling) }
280     catch_frame:=implicitframe and
281       (current_procinfo.procdef.proccalloption=pocall_safecall);
282 
283     oldflowcontrol:=flowcontrol;
284     flowcontrol:=[fc_inflowcontrol];
285 
286     templabel:=nil;
287     current_asmdata.getjumplabel(trylabel);
288     current_asmdata.getjumplabel(endtrylabel);
289     current_asmdata.getjumplabel(finallylabel);
290     current_asmdata.getjumplabel(endfinallylabel);
291     oldexitlabel:=current_procinfo.CurrExitLabel;
292     if implicitframe then
293       current_procinfo.CurrExitLabel:=finallylabel;
294 
295     { Start of scope }
296     { Padding with NOP is necessary here because exceptions in called
297       procedures are seen at the next instruction, while CPU/OS exceptions
298       like AV are seen at the current instruction.
299 
300       So in the following code
301 
302       raise_some_exception;        //(a)
303       try
304         pchar(nil)^:='0';          //(b)
305         ...
306 
307       without NOP, exceptions (a) and (b) will be seen at the same address
308       and fall into the same scope. However they should be seen in different scopes.
309     }
310 
311     emit_nop;
312     cg.a_label(current_asmdata.CurrAsmList,trylabel);
313 
314     { try code }
315     if assigned(left) then
316       begin
317         { fc_unwind_xx tells exit/continue/break statements to emit special
318           unwind code instead of just JMP }
319         if not implicitframe then
320           flowcontrol:=flowcontrol+[fc_catching_exceptions,fc_unwind_exit,fc_unwind_loop];
321         secondpass(left);
322         flowcontrol:=flowcontrol-[fc_catching_exceptions,fc_unwind_exit,fc_unwind_loop];
323         if codegenerror then
324           exit;
325       end;
326 
327     { finallylabel is only used in implicit frames as an exit point from nested try..finally
328       statements, if any. To prevent finalizer from being executed twice, it must come before
329       endtrylabel (bug #34772) }
330     if catch_frame then
331       begin
332         current_asmdata.getjumplabel(templabel);
333         cg.a_label(current_asmdata.CurrAsmList, finallylabel);
334         { jump over exception handler }
335         cg.a_jmp_always(current_asmdata.CurrAsmList,templabel);
336         { Handle the except block first, so endtrylabel serves both
337           as end of scope and as unwind target. This way it is possible to
338           encode everything into a single scope record. }
339         cg.a_label(current_asmdata.CurrAsmList,endtrylabel);
340         if (current_procinfo.procdef.proccalloption=pocall_safecall) then
341           begin
342             handle_safecall_exception;
343             cg.a_jmp_always(current_asmdata.CurrAsmList,endfinallylabel);
344           end
345         else
346           InternalError(2014031601);
347         cg.a_label(current_asmdata.CurrAsmList,templabel);
348       end
349     else
350       begin
351         { same as emit_nop but using finallylabel instead of dummy }
352         cg.a_label(current_asmdata.CurrAsmList,finallylabel);
353         finallylabel.increfs;
354         current_asmdata.CurrAsmList.concat(Taicpu.op_none(A_NOP,S_NO));
355         cg.a_label(current_asmdata.CurrAsmList,endtrylabel);
356       end;
357 
358     { i32913 - if the try..finally block is also inside a try..finally or
359       try..except block, make a note of any Exit calls so all necessary labels
360       are generated. [Kit] }
361     if ((flowcontrol*[fc_exit,fc_break,fc_continue])<>[]) and (fc_inflowcontrol in oldflowcontrol) then
362       oldflowcontrol:=oldflowcontrol+(flowcontrol*[fc_exit,fc_break,fc_continue]);
363 
364     flowcontrol:=[fc_inflowcontrol];
365     { generate finally code as a separate procedure }
366     if not implicitframe then
367       tcgprocinfo(current_procinfo).generate_exceptfilter(finalizepi);
368     { right is a call to finalizer procedure }
369     secondpass(right);
370 
371     if codegenerror then
372       exit;
373 
374     { normal exit from safecall proc must zero the result register }
375     if implicitframe and (current_procinfo.procdef.proccalloption=pocall_safecall) then
376       cg.a_load_const_reg(current_asmdata.CurrAsmList,OS_INT,0,NR_FUNCTION_RESULT_REG);
377 
378     cg.a_label(current_asmdata.CurrAsmList,endfinallylabel);
379 
380     { generate the scope record in .xdata }
381     tcpuprocinfo(current_procinfo).add_finally_scope(trylabel,endtrylabel,
catch_framenull382       current_asmdata.RefAsmSymbol(finalizepi.procdef.mangledname,AT_FUNCTION),catch_frame);
383 
384     if implicitframe then
385       current_procinfo.CurrExitLabel:=oldexitlabel;
386     flowcontrol:=oldflowcontrol;
387   end;
388 
389 { tx64tryexceptnode }
390 
391 procedure tx64tryexceptnode.pass_generate_code;
392   var
393     trylabel,
394     exceptlabel,oldendexceptlabel,
395     lastonlabel,
396     exitexceptlabel,
397     continueexceptlabel,
398     breakexceptlabel,
399     oldCurrExitLabel,
400     oldContinueLabel,
401     oldBreakLabel : tasmlabel;
402     onlabel,
403     filterlabel: tasmlabel;
404     oldflowcontrol,tryflowcontrol,
405     exceptflowcontrol : tflowcontrol;
406     hnode : tnode;
407     hlist : tasmlist;
408     onnodecount : tai_const;
409   label
410     errorexit;
411   begin
412     if (target_info.system<>system_x86_64_win64) then
413       begin
414         inherited pass_generate_code;
415         exit;
416       end;
417     location_reset(location,LOC_VOID,OS_NO);
418 
419     oldflowcontrol:=flowcontrol;
420     exceptflowcontrol:=[];
421     continueexceptlabel:=nil;
422     breakexceptlabel:=nil;
423 
424     include(flowcontrol,fc_inflowcontrol);
425     { this can be called recursivly }
426     oldBreakLabel:=nil;
427     oldContinueLabel:=nil;
428     oldendexceptlabel:=endexceptlabel;
429 
430     { save the old labels for control flow statements }
431     oldCurrExitLabel:=current_procinfo.CurrExitLabel;
432     current_asmdata.getjumplabel(exitexceptlabel);
433     if assigned(current_procinfo.CurrBreakLabel) then
434       begin
435         oldContinueLabel:=current_procinfo.CurrContinueLabel;
436         oldBreakLabel:=current_procinfo.CurrBreakLabel;
437         current_asmdata.getjumplabel(breakexceptlabel);
438         current_asmdata.getjumplabel(continueexceptlabel);
439       end;
440 
441     current_asmdata.getjumplabel(exceptlabel);
442     current_asmdata.getjumplabel(endexceptlabel);
443     current_asmdata.getjumplabel(lastonlabel);
444     filterlabel:=nil;
445 
446     { start of scope }
447     current_asmdata.getjumplabel(trylabel);
448     emit_nop;
449     cg.a_label(current_asmdata.CurrAsmList,trylabel);
450 
451     { control flow in try block needs no special handling,
452       just make sure that target labels are outside the scope }
453     secondpass(left);
454     tryflowcontrol:=flowcontrol;
455     if codegenerror then
456       goto errorexit;
457 
458     { jump over except handlers }
459     cg.a_jmp_always(current_asmdata.CurrAsmList,endexceptlabel);
460 
461     { end of scope }
462     cg.a_label(current_asmdata.CurrAsmList,exceptlabel);
463 
464     { set control flow labels for the except block }
465     { and the on statements                        }
466     current_procinfo.CurrExitLabel:=exitexceptlabel;
467     if assigned(oldBreakLabel) then
468       begin
469         current_procinfo.CurrContinueLabel:=continueexceptlabel;
470         current_procinfo.CurrBreakLabel:=breakexceptlabel;
471       end;
472 
473     { i32913 - if the try..finally block is also inside a try..finally or
474       try..except block, make a note of any Exit calls so all necessary labels
475       are generated. [Kit] }
476     if ((flowcontrol*[fc_exit,fc_break,fc_continue])<>[]) and (fc_inflowcontrol in oldflowcontrol) then
477       oldflowcontrol:=oldflowcontrol+(flowcontrol*[fc_exit,fc_break,fc_continue]);
478 
479     flowcontrol:=[fc_inflowcontrol];
480     { on statements }
481     if assigned(right) then
482       begin
483         { emit filter table to a temporary asmlist }
484         hlist:=TAsmList.Create;
485         current_asmdata.getaddrlabel(filterlabel);
486         new_section(hlist,sec_rodata_norel,filterlabel.name,4);
487         cg.a_label(hlist,filterlabel);
488         onnodecount:=tai_const.create_32bit(0);
489         hlist.concat(onnodecount);
490 
491         hnode:=right;
492         while assigned(hnode) do
493           begin
494             if hnode.nodetype<>onn then
495               InternalError(2011103101);
496             current_asmdata.getjumplabel(onlabel);
497             hlist.concat(tai_const.create_rva_sym(current_asmdata.RefAsmSymbol(tonnode(hnode).excepttype.vmt_mangledname,AT_DATA)));
498             hlist.concat(tai_const.create_rva_sym(onlabel));
499             cg.a_label(current_asmdata.CurrAsmList,onlabel);
500             secondpass(hnode);
501             inc(onnodecount.value);
502             hnode:=tonnode(hnode).left;
503           end;
504         { add 'else' node to the filter list, too }
505         if assigned(t1) then
506           begin
507             hlist.concat(tai_const.create_32bit(-1));
508             hlist.concat(tai_const.create_rva_sym(lastonlabel));
509             inc(onnodecount.value);
510           end;
511         { now move filter table to permanent list all at once }
512         current_procinfo.aktlocaldata.concatlist(hlist);
513         hlist.free;
514       end;
515 
516     cg.a_label(current_asmdata.CurrAsmList,lastonlabel);
517     if assigned(t1) then
518       begin
519         { here we don't have to reset flowcontrol           }
520         { the default and on flowcontrols are handled equal }
521         secondpass(t1);
522         cg.g_call(current_asmdata.CurrAsmList,'FPC_DONEEXCEPTION');
523         if (flowcontrol*[fc_exit,fc_break,fc_continue]<>[]) then
524           cg.a_jmp_always(current_asmdata.CurrAsmList,endexceptlabel);
525       end;
526     exceptflowcontrol:=flowcontrol;
527 
528     if fc_exit in exceptflowcontrol then
529       begin
530         { do some magic for exit in the try block }
531         cg.a_label(current_asmdata.CurrAsmList,exitexceptlabel);
532         cg.g_call(current_asmdata.CurrAsmList,'FPC_DONEEXCEPTION');
533         if (fc_unwind_exit in oldflowcontrol) then
534           cg.g_local_unwind(current_asmdata.CurrAsmList,oldCurrExitLabel)
535         else
536           cg.a_jmp_always(current_asmdata.CurrAsmList,oldCurrExitLabel);
537       end;
538 
539     if fc_break in exceptflowcontrol then
540       begin
541         cg.a_label(current_asmdata.CurrAsmList,breakexceptlabel);
542         cg.g_call(current_asmdata.CurrAsmList,'FPC_DONEEXCEPTION');
543         if (fc_unwind_loop in oldflowcontrol) then
544           cg.g_local_unwind(current_asmdata.CurrAsmList,oldBreakLabel)
545         else
546           cg.a_jmp_always(current_asmdata.CurrAsmList,oldBreakLabel);
547       end;
548 
549     if fc_continue in exceptflowcontrol then
550       begin
551         cg.a_label(current_asmdata.CurrAsmList,continueexceptlabel);
552         cg.g_call(current_asmdata.CurrAsmList,'FPC_DONEEXCEPTION');
553         if (fc_unwind_loop in oldflowcontrol) then
554           cg.g_local_unwind(current_asmdata.CurrAsmList,oldContinueLabel)
555         else
556           cg.a_jmp_always(current_asmdata.CurrAsmList,oldContinueLabel);
557       end;
558 
559     emit_nop;
560     cg.a_label(current_asmdata.CurrAsmList,endexceptlabel);
561     tcpuprocinfo(current_procinfo).add_except_scope(trylabel,exceptlabel,endexceptlabel,filterlabel);
562 
563 errorexit:
564     { restore all saved labels }
565     endexceptlabel:=oldendexceptlabel;
566 
567     { i32913 - if the try..finally block is also inside a try..finally or
568       try..except block, make a note of any Exit calls so all necessary labels
569       are generated. [Kit] }
570     if ((flowcontrol*[fc_exit,fc_break,fc_continue])<>[]) and (fc_inflowcontrol in oldflowcontrol) then
571       oldflowcontrol:=oldflowcontrol+(flowcontrol*[fc_exit,fc_break,fc_continue]);
572 
573     { restore the control flow labels }
574     current_procinfo.CurrExitLabel:=oldCurrExitLabel;
575     if assigned(oldBreakLabel) then
576       begin
577         current_procinfo.CurrContinueLabel:=oldContinueLabel;
578         current_procinfo.CurrBreakLabel:=oldBreakLabel;
579       end;
580 
581     { return all used control flow statements }
582     flowcontrol:=oldflowcontrol+(exceptflowcontrol +
583       tryflowcontrol - [fc_inflowcontrol]);
584   end;
585 
586 initialization
587   craisenode:=tx64raisenode;
588   connode:=tx64onnode;
589   ctryexceptnode:=tx64tryexceptnode;
590   ctryfinallynode:=tx64tryfinallynode;
591 end.
592 
593