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