Recent

Author Topic: wondering try.. finally structure  (Read 832 times)

egsuh

  • Hero Member
  • *****
  • Posts: 1696
wondering try.. finally structure
« on: September 02, 2025, 01:33:18 pm »
I'm thinking of a structure like following.

Code: Pascal  [Select][+][-]
  1. begin
  2.      for i := 0 to SomeNumber do begin
  3.          AnObject := TAnObject.create;
  4.          AnFPGMap.Add(properName, AnObject);
  5.  
  6.          try
  7.              if SomeCondition then begin
  8.                 // Add more TAnObjects to AnFPGMap;
  9.              end
  10.              else begin
  11.                   { // I can writhe this part without try... finally ......
  12.                   for tj := 0 to AnAPGMap.Count-1 do
  13.                       AnFPGMapObject.Add(AnFPGMap.Key[tj], AnFPGMap.Data[tj]);
  14.                   AnFPGMap.Clear;                   // }
  15.                   Continue
  16.               end;
  17.  
  18.               // do many other things
  19.              
  20.          finally
  21.               for tj := 0 to AnAPGMap.Count-1 do
  22.                    AnFPGMapObject.Add(AnFPGMap.Key[tj], AnFPGMap.Data[tj]);
  23.               AnFPGMap.Clear;
  24.          end;
  25.     end;
  26. end;

This code is messy, but my question is regarding the following part before "continue" and within finally section.

Code: Pascal  [Select][+][-]
  1.               for tj := 0 to AnAPGMap.Count-1 do
  2.                    AnFPGMapObject.Add(AnFPGMap.Key[tj], AnFPGMap.Data[tj]);
  3.               AnFPGMap.Clear;
  4.  

First, I can write it twice without try..finally structure, which is current practice.

My question is whether I can write it within try..finally, which is within for loop as shown in the above example, because finally section should be executed even with continue or exit.

Second question is aren't there any loss of performance, which I don't really care. But just want to know.

Thaddy

  • Hero Member
  • *****
  • Posts: 18364
  • Here stood a man who saw the Elbe and jumped it.
Re: wondering try.. finally structure
« Reply #1 on: September 02, 2025, 04:37:36 pm »
try / finally inside the loop will slow down your code. Don't do that for time critical loops.
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

Khrys

  • Sr. Member
  • ****
  • Posts: 348
Re: wondering try.. finally structure
« Reply #2 on: September 03, 2025, 08:19:27 am »
[...] because finally section should be executed even with continue or exit.

Yes, the  finally  section is executed even on  continuebreak  or  exit.
Not even  goto  can bypass  finally  (because attempting to use  goto  in an exception block leads to a compiler error) - so unless the process is killed by the OS or some nasty stack corruption occurs, there seems to be no way for  finally  to fail. Please correct me if I'm wrong!

Second question is aren't there any loss of performance, which I don't really care. But just want to know.

Like @Thaddy already said,  try..finally  does negatively impact performance. Take a look at the disassembly of these two functions differing only in the use of  try..finally  (compiled on  x86_64-linux  using  FPC 3.2.2):

Code: Pascal  [Select][+][-]
  1. procedure Test_1();
  2. begin
  3.   Foo();
  4.   Bar();
  5.   Fizz();
  6. end;

Code: ASM  [Select][+][-]
  1. lea   rsp, [rsp - 8]
  2. call  Foo
  3. call  Bar
  4. call  Fizz
  5. lea   rsp, [rsp + 8]
  6. ret

Very straightforward disassembly, does pretty much exactly what you'd expect.
Now watch what happens when we add a  try..finally  block:

Code: Pascal  [Select][+][-]
  1. procedure Test_2();
  2. begin
  3.   try
  4.     Foo();
  5.     Bar();
  6.   finally
  7.     Fizz();
  8.   end;
  9. end;

Code: ASM  [Select][+][-]
  1. ; allocate space for jump buffer
  2. lea   rsp, [rsp - 104]
  3.  
  4. ; jmp_buf = fpc_pushexceptaddr(Ft = 1 {finally}, _buf = local@+24, _newaddr = local@+0)
  5. mov   rdx, rsp
  6. lea   rsi, [rsp + 24]
  7. mov   edi, 1
  8. call  fpc_pushexceptaddr
  9. mov   rdi, rax
  10.  
  11. ; fpc_setjmp(jmp_buf); saves all nonvolatile registers (expensive!) and returns 0 in eax
  12. call  fpc_setjmp
  13.  
  14. ; !!! raising an exception moves the instruction pointer here (with eax <> 0)
  15. movsxd  rdx, eax
  16. mov     QWORD PTR [rsp + 88], rdx
  17.  
  18. ; execute body only right after fpc_setjmp (when eax = 0)
  19. test  eax, eax
  20. jne   .finally
  21.  
  22. ; 'try' body
  23. call  Foo
  24. call  Bar
  25.  
  26. ; forget about jmp_buf (counterpart to fpc_pushexceptaddr) and execute 'finally' block
  27. .finally
  28. call  fpc_popaddrstack
  29.  
  30. ; 'finally' body
  31. call  Fizz
  32.  
  33. ; re-raise caught exception if one occurred (when eax <> 0 after fpc_setjmp)
  34. mov   rax, QWORD PTR [rsp + 88]
  35. test  rax, rax
  36. je    .done
  37.  
  38. call  fpc_reraise
  39.  
  40. ; end of try..finally block - return from procedure
  41. .done
  42. lea   rsp, [rsp + 104]
  43. ret

Merely setting up an exception handler (i.e. entering a  try  block) is expensive in Pascal, which handles exceptions differently than C++ does:
  • C++ exception handlers are zero-cost to set up, but exceptions are very expensive to catch.
  • Pascal (FPC) exception handlers are somewhat costly to set up, but exceptions are not too expensive to catch.

In other words, using  try..finally/except  always has a performance impact, even if no exceptions are actually raised.
There's a tradeoff between "happy path" speed and "unhappy path" speed, and the FPC developers chose balance over imbalance.

ASerge

  • Hero Member
  • *****
  • Posts: 2466
Re: wondering try.. finally structure
« Reply #3 on: September 03, 2025, 07:33:57 pm »
  • Pascal (FPC) exception handlers are somewhat costly to set up, but exceptions are not too expensive to catch.
In Windows (x64) a very easy prologue:
Code: ASM  [Select][+][-]
  1. .seh_proc P$PROJECT1_$$_TEST_2
  2. # [8] begin
  3.         pushq   %rbp
  4. .seh_pushreg %rbp
  5.         movq    %rsp,%rbp
  6.         leaq    -32(%rsp),%rsp
  7. .seh_stackalloc 32
  8. .seh_endprologue
  9. # [9] try
  10.         nop
  11. .Lj13:
  12. # [10] Foo;
  13.         call    P$PROJECT1_$$_FOO
  14. # [11] Bar;
  15.         call    P$PROJECT1_$$_BAR
  16.         nop
  17.         movq    %rbp,%rcx
  18. .Lj14:
  19.         call    P$PROJECT1$_$TEST_2_$$_fin$00000004
  20. # [15] end;
  21.         nop
  22.         leaq    (%rbp),%rsp
  23.         popq    %rbp
  24.         ret
  25. .seh_handler __FPC_specific_handler,@unwind
  26. .seh_handlerdata
  27.         .long   1
  28.         .long   0
  29.         .rva    .Lj13
  30.         .rva    .Lj14
  31.         .rva    P$PROJECT1$_$TEST_2_$$_fin$00000004
Code: ASM  [Select][+][-]
  1. .seh_proc P$PROJECT1$_$TEST_2_$$_fin$00000004
  2. # [14] end;
  3.         pushq   %rbp
  4. .seh_pushreg %rbp
  5.         movq    %rcx,%rbp
  6.         leaq    -32(%rsp),%rsp
  7. .seh_stackalloc 32
  8. .seh_endprologue
  9. # [13] Fizz;
  10.         call    P$PROJECT1_$$_FIZZ
  11.         nop
  12.         leaq    32(%rsp),%rsp
  13.         popq    %rbp
  14.         ret

PascalDragon

  • Hero Member
  • *****
  • Posts: 6195
  • Compiler Developer
Re: wondering try.. finally structure
« Reply #4 on: September 05, 2025, 10:42:00 pm »
Merely setting up an exception handler (i.e. entering a  try  block) is expensive in Pascal, which handles exceptions differently than C++ does:
  • C++ exception handlers are zero-cost to set up, but exceptions are very expensive to catch.
  • Pascal (FPC) exception handlers are somewhat costly to set up, but exceptions are not too expensive to catch.

How exceptions are done in Pascal is implementation specific, you can't generalize here. Exceptions on i386-win32 and x86_64-win64 are cheaper due to the mechanisms provided by Windows. FPC main also has experimental support for DWARF exceptions on Linux which are also cheaper however they require linking against libunwind.a even for statically linked binaries like the compiler...

 

TinyPortal © 2005-2018