Recent

Author Topic: Non-local goto example needed  (Read 3012 times)

PascalDragon

  • Hero Member
  • *****
  • Posts: 5764
  • Compiler Developer
Re: Non-local goto example needed
« Reply #15 on: May 27, 2023, 06:56:14 pm »
For the demonstrated purpose setjmp()-longjmp() should be better, because it saves/restores registers and gives a return value.

non-local-gotos are implemented using setjmp/longjmp.

I have never tried nonlocal gotos. Theoretically these could be used to jump into a loop or into a procedure, which is a horrible idea for me.

With setjmp/longjmp you can do that as well, but unlike with non-local-gotos the compiler can't forbid critical uses, because these two functions are just ordinary functions and the compiler does not know what they do.

With setjmp/longjmp you must initialize jmp_buf, which stores the location and registers and you can return and you get a return value. You can only jump back to a location, where you have been before you cannot jump to arbitrary locations.
This is very different.

Of course you can use setjmp/longjmp to jump from one function to another, that's what makes them so dangerous:

Code: Pascal  [Select][+][-]
  1. program tsetjmp;
  2.  
  3. var
  4.   buf: jmp_buf;
  5.  
  6. procedure Test;
  7. begin
  8.   Writeln('Setting jmp_buf');
  9.   if setjmp(buf) <> 0 then
  10.     Writeln('Jumped')
  11.   else
  12.     Writeln('No jump');
  13.   Writeln('In Test');
  14. end;
  15.  
  16. procedure Test2;
  17. begin
  18.   Writeln('And... Jump!');
  19.   LongJmp(buf, 1);
  20.   Writeln('This won''t be visible');
  21. end;
  22.  
  23. begin
  24.   Test;
  25.   Test2;
  26. end.

This will print:

Code: [Select]
C:\fpc\git>.\testoutput\tsetjmp.exe
Setting jmp_buf
No jump
In Test
And... Jump!
Jumped
In Test

Experimental code example:  Variable "pos" is the jmp_buf, which stores registers and execution point.

Code: Pascal  [Select][+][-]
  1.     while True do
  2.  
  3.     try
  4.       while (true) do
  5.       begin
  6.         if setjmp(pos) > 0 then    //"pos" stores registers and execution point here into jmp_buf record.
  7.            writeln('Error! Please input integer number!');
  8.         readln(N);
  9.        ...... //code which may raise exception can modify "pos" so
  10.        ......//the exception always returns to the point where it was raised.
  11.       end;
  12.     except
  13.       on E: EInOutError do
  14.       begin
  15.         longjmp(pos, 1);
  16.         // Writeln(E.ClassName, ': ', E.Message);
  17.       end;
  18.     end;

Simplifies error handling, if you have multiple "readln" lines which can raise exceptions in the code.

Your code results in a memory leak, because the exception instance won't be freed:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2.  
  3. uses
  4.   SysUtils;
  5.  
  6. var
  7.   buf: jmp_buf;
  8. begin
  9.   try
  10.     if setjmp(buf) <> 0 then begin
  11.       Writeln('Jumped');
  12.       Exit;
  13.     end;
  14.     raise Exception.Create('Test');
  15.   except
  16.     on E: Exception do begin
  17.       Writeln('Caught exception, jumping');
  18.       longjmp(buf, 1);
  19.     end;
  20.   end;
  21. end.

Compiled with -ghlw2 this will be the result:

Code: [Select]
C:\fpc\git>.\testoutput\texcept.exe
Caught exception, jumping
Jumped
Heap dump by heaptrc unit of C:\fpc\git\testoutput\texcept.exe
97 memory blocks allocated : 3454/3752
95 memory blocks freed     : 3390/3688
2 unfreed memory blocks : 64
True heap size : 163840 (160 used in System startup)
True free heap : 163168
Should be : 163232
Call trace for block $0000000000118A40 size 40
  $0000000100008BBB
  $000000010000E046
  $000000010000E4F5
  $00007FFECA6323DF
  $00007FFECA5E14A4
  $00007FFECA5E11F5
  $00007FFEC821CF19
  $000000010000DEFB
  $0000000100001798  main,  line 14 of fpctests/texcept.pp
  $0000000100001806  main,  line 21 of fpctests/texcept.pp
  $000000010000DBB0
  $0000000100001700
  $00007FFEC9BC7614
  $00007FFECA5E26A1
  $00007FFECA5E26A1
Call trace for block $0000000000118B40 size 24
  $0000000100008B42
  $000000010000736A
  $000000010001693A
  $0000000100001786  main,  line 14 of fpctests/texcept.pp
  $0000000100001806  main,  line 21 of fpctests/texcept.pp
  $000000010000DBB0
  $0000000100001700
  $00007FFEC9BC7614
  $00007FFECA5E26A1
  $000000010001889A
  $00000001000080BC
  $0000000100001722  main,  line 8 of fpctests/texcept.pp
  $0000000100001806  main,  line 21 of fpctests/texcept.pp
  $000000010000DBB0
  $0000000100001700
  $00007FFEC9BC7614

Line 14 is the line where the exception is raised.

Can you do this with nonlocal goto?

No, because for the above reason the compiler forbids a non-local-goto out of a control-flow statement like a tryfinally- or tryexcept-block and thus is in fact safer than using setjmp/longjmp directly (though I would use neither for something like this).

non-local-gotos are implemented using setjmp/longjmp.

What about exceptions? They are also implemented in this way?

At least on targets that don't have their own exception handling mechanism (i386-win32, x86_64-win64, aarch64-win64 and in trunk as an option also some POSIX targets like Linux) setjmp/longjmp are used to implement it, however the compiler ensures that they're only used in a correct way (e.g. no jumping to a foreign stack frame).

BobDog

  • Sr. Member
  • ****
  • Posts: 394
Re: Non-local goto example needed
« Reply #16 on: May 27, 2023, 07:55:17 pm »
What about using asm?
Code: Pascal  [Select][+][-]
  1. {$GOTO ON}
  2.  
  3. label
  4. lbl,nest;
  5.  
  6. procedure one;
  7. const f:integer=0;
  8. procedure nested;
  9. begin
  10. writeln('HELLO');
  11. asm nest: end;
  12. f:=f+1;
  13. writeln('nested in one ');
  14. if (f=2) or (f=4) then writeln(' press return  . . .');
  15. asm ret end;
  16. writeln ('unseen');
  17. end;
  18.  
  19. begin
  20. writeln('Proc one start');
  21. asm call nest end;
  22. exit;
  23. asm lbl: end;
  24. writeln('Proc one at label:');
  25. asm ret end;
  26. writeln ('unseen');
  27. end;
  28.  
  29.  
  30. procedure two;
  31. begin
  32. writeln('Proc two');
  33. asm call lbl end;
  34. end;
  35.  
  36.  
  37. begin
  38. one;
  39. two;
  40. asm call nest end;
  41. readln;
  42.  
  43. one;
  44. two;
  45. asm call nest end;
  46. readln;
  47. end.
  48.  
  49.  

Also for 64 bits:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2.  {$GOTO ON}
  3.  
  4. uses
  5.   SysUtils;
  6.  label
  7. lbl;
  8. var
  9.  started:BOOLEAN=false;
  10.  
  11. begin
  12.   try
  13. asm lbl: end;
  14.    if started then begin
  15.       Writeln('Jumped . . .');
  16. asm ret end;
  17.     end;
  18.     raise Exception.Create('Test');
  19.   except
  20.     on E: Exception do begin
  21.       Writeln('Caught exception, jumping');
  22.       started:=true;
  23. asm call lbl end;
  24.     end;
  25.   end;
  26. end.
  27.  

option -ghlw2 as advised previously.
« Last Edit: May 28, 2023, 12:18:46 am by BobDog »

Peter H

  • Sr. Member
  • ****
  • Posts: 272
Re: Non-local goto example needed
« Reply #17 on: May 27, 2023, 08:05:34 pm »
@Pascaldragon
Ok this causes a memory leak.
What I want to do:
I want to jump back to the point before the line which raised the exception and return an error code.
This enables me to give a helpful error message, to try again and fix the problem.
I hope, I can then do this:
(This is experimental code, which I truncated for clarity)
Anyway, should be easier if I disable exceptions and use lasterror then.....
I do not want exceptions if they do not give me a chance to try again and fix the problem...
Code: Pascal  [Select][+][-]
  1.   procedure main;
  2.   var
  3.     i: integer;
  4.     N2, N, root: int64;
  5.     rest: int64;
  6.     base: integer;
  7.     pos: jmp_buf;
  8.     error: boolean;
  9.   const
  10.     NL = #13#10;
  11.   begin
  12.     Initialize(pos);   //avoid warning
  13.  
  14.     while True do begin
  15.     try
  16.       error := False;
  17.       if setjmp(pos) > 0 then writeln('Bitte Zahl eingeben');
  18.       readln(N);
  19.       // other code that can raise exceptions
  20.       //....
  21.     except
  22.       on E: EInOutError do
  23.         error := True;
  24.       // Writeln(E.ClassName, ': ', E.Message);
  25.     end;
  26.     if error then
  27.       longjmp(pos, 1);
  28.      end;
  29.   end;
  30.  
This code seems to confuse the debugger, if I input an invalid integer. ;-)
« Last Edit: May 27, 2023, 09:07:21 pm by Peter H »

alpine

  • Hero Member
  • *****
  • Posts: 1303
Re: Non-local goto example needed
« Reply #18 on: May 28, 2023, 10:14:15 am »
I want to jump back to the point before the line which raised the exception and return an error code.
This enables me to give a helpful error message, to try again and fix the problem.
But why you should bother with so peculiar thing as non-local goto, shouldn't it be so much easier to write it like:
Code: Pascal  [Select][+][-]
  1.   error := False;
  2.   repeat
  3.     try
  4.       if Error then writeln('Bitte Zahl eingeben');
  5.       readln(N);
  6.       // other code that can raise exceptions
  7.       //....
  8.     except
  9.       on E: EInOutError do
  10.         error := True;
  11.     end;
  12.   until not error;
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Peter H

  • Sr. Member
  • ****
  • Posts: 272
Re: Non-local goto example needed
« Reply #19 on: May 28, 2023, 12:26:11 pm »
It must jump over lines which already where successful executed.

I think, this works:

Code: Pascal  [Select][+][-]
  1.   procedure main;
  2.   var
  3.     i: integer;
  4.     N2, N, root: int64;
  5.     rest: int64;
  6.     base: integer;
  7.     pos: jmp_buf;
  8.     error: boolean;
  9.   const
  10.     NL = #13#10;
  11.   begin
  12.     Initialize(pos);   //avoid warning
  13.     error := False;
  14.     while True do
  15.     try
  16.       if error then //try to fix the last error
  17.       begin
  18.         error := False;
  19.         longjmp(pos, 1);
  20.       end;
  21.  
  22.       if setjmp(pos) > 0 then writeln('Error! Bitte Zahl eingeben');
  23.       readln(N);
  24.       if setjmp(pos) > 0 then writeln('Error! Input integer value 1..10!');
  25.       readln(N2);
  26.       if (N2<1) or (N2>10) then Raise EInOutError.Create('Range Error!');;
  27.  
  28.       // other code which can raise exceptions follows here
  29.       // If always setjmp is used, program will jump to last error.
  30.       // ..........
  31.     except
  32.       on E: EInOutError do
  33.       begin
  34.         error := True;
  35.         Writeln(E.ClassName, ': ', E.Message);
  36.       end;
  37.     end;
  38.   end;

Of course this can also be done with a state machine and without longjmp, but not so easy.
« Last Edit: May 28, 2023, 04:51:54 pm by Peter H »

alpine

  • Hero Member
  • *****
  • Posts: 1303
Re: Non-local goto example needed
« Reply #20 on: May 28, 2023, 12:50:52 pm »
It must jump over lines which already where successful executed.

Then consider:
Code: Pascal  [Select][+][-]
  1.   progress := 0; error = false;
  2.   while True do
  3.     try
  4.       case progress of
  5.         0:
  6.           begin
  7.             if error then writeln('Bitte Zahl eingeben');
  8.             readln(N);
  9.             Inc(progress); error := false;
  10.           end;
  11.         1:
  12.           begin
  13.             // other code that can raise exceptions
  14.             //....
  15.             Inc(progress); error := false;
  16.           end;
  17.       otherwise
  18.         Break;
  19.       end;
  20.     except
  21.       on E: EInOutError do
  22.         error := True;
  23.     end;
  24.  
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Peter H

  • Sr. Member
  • ****
  • Posts: 272
Re: Non-local goto example needed
« Reply #21 on: May 28, 2023, 01:03:14 pm »
Essentially case also uses a jumptable under the hood, it is a programmed jump.
I find my solution better, but this is a matter of taste.

alpine

  • Hero Member
  • *****
  • Posts: 1303
Re: Non-local goto example needed
« Reply #22 on: May 28, 2023, 01:56:19 pm »
Essentially case also uses a jumptable under the hood, it is a programmed jump.
I find my solution better, but this is a matter of taste.
With case you're not messing with PC,SP, or at least not directly. With longjmp you're not restoring the whole execution context, as you have seen that it can "leak". But otherwise it looks neater - at least in source.   
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

 

TinyPortal © 2005-2018