Recent

Author Topic: For variable being read after loop... Any way to get an error or warning on it?  (Read 13313 times)

Joanna

  • Hero Member
  • *****
  • Posts: 1105
I didn’t know that a for loop was a cpu register I always imagined that it was just the variable being incremented automatically.
I often use the index inside the for loop for various things such as case statements...
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  #pascal Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

440bx

  • Hero Member
  • *****
  • Posts: 4570
I didn’t know that a for loop was a cpu register
The index variable _isn't_ always placed in a CPU register.  Whether the compiler decides to place it in a register or not also depends on what is done with the index in the loop but, the fact that the compiler does not normally have to worry about the index having a defined value at the end of the loop makes the "index in register" optimization easier to implement and more common than if the compiler had to guarantee the value no matter how the loop ended.

There are a lot of rules of what is allowed and not allowed that are in place to allow code optimization.  Another example is the fact that a "case" statement only allows constants.  It does that because that's the only way it may be able to create a jump table to each case.  Implementing a fully general "case" that allows variables against variables isn't hard at all (just a linear sequence of hidden "if" statements that share a common exit point, actually, it's fairly trivial but cannot be "jumptable-ized".)

Just like the index in a "for" loop isn't always placed in a register, a "case" statement isn't always implemented using a jump table but, the restriction makes it easier to implement.

Another way "case" statements are implemented is by subtract and compare which is quite efficient and depends on the cases being constants too.

Interpreters usually have much fewer restrictions because quite a few optimizations that can be done in compiled code cannot be done in interpreted code (interpreted code can be self modifying or generated on the fly during execution and the interpreter is a "static engine"), that's one of the many reasons interpreters are much more flexible but also orders of magnitude slower.

In many cases there are usually good reasons behind the restrictions in a computer language (if they aren't good, they'll usually be removed in subsequent revisions.)

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Khrys

  • Jr. Member
  • **
  • Posts: 98
The index variable _isn't_ always placed in a CPU register.

Case in point: I just tested the following two equivalent loops for summing a bunch of integers (FPC trunk, x86-64, Linux, -O4 optimization):

Code: Pascal  [Select][+][-]
  1. procedure Test();
  2. var
  3.   I, J: SizeInt;
  4.   Sum: SizeInt;
  5. begin
  6.   // For loop
  7.   Sum := 0;
  8.   for I := 0 to 999 do begin
  9.     Inc(Sum, I);
  10.   end;
  11.   // While loop
  12.   Sum := 0;
  13.   J := 0;
  14.   while J < 1000 do begin
  15.     Inc(Sum, J);
  16.     Inc(J);
  17.   end;
  18. end;

The compiler didn't place the index variable in a register in either case.
For  loop:

Code: ASM  [Select][+][-]
  1.     mov [rbp - 8], 0
  2. .for_start:
  3.     mov rax, [rbp - 8]
  4.     add [rbp - 24], rax
  5.     add [rbp - 8], 1
  6.     cmp [rbp - 8], 999
  7.     jnge .for_start

While  loop:

Code: ASM  [Select][+][-]
  1.     mov [rbp - 16], 0
  2.     jmp .while_cond
  3. .while_start:
  4.     mov rax, [rbp - 16]
  5.     add [rbp - 24], rax
  6.     add [rbp - 16], 1
  7. .while_cond:
  8.     cmp [rbp - 16], 1000
  9.     jl .while_start

Apart from a single additional  jmp  at the beginning of the  while  loop, the generated code happens to be completely identical.

440bx

  • Hero Member
  • *****
  • Posts: 4570
@Khrys,

At least in the case of the "for" loop I would have thought that for such a simple loop the compiler would have placed the index variable in a register.

Good additions, thank you.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

LV

  • Jr. Member
  • **
  • Posts: 79
Reading this topic, I had a thought. Should a doctor, engineer, or scientist, when working with the Pascal language (or any other language for that matter), be looking under the hood and analyzing the assembly code and optimizations? I think the user should simply follow the rules of the language and let the compiler do the rest. It's great if the programmer makes a mistake and the compiler points it out!

440bx

  • Hero Member
  • *****
  • Posts: 4570
Reading this topic, I had a thought. Should a doctor, engineer, or scientist, when working with the Pascal language (or any other language for that matter), be looking under the hood and analyzing the assembly code and optimizations?
In theory the answer is no but, in practice the answer could occasionally be yes.  When performance really matters then looking at the code the compiler generated is "prophylactic".

I think the user should simply follow the rules of the language and let the compiler do the rest. It's great if the programmer makes a mistake and the compiler points it out!
Absolutely!.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

MarkMLl

  • Hero Member
  • *****
  • Posts: 7725
Joanna, I'm taking the liberty of mis-quoting you by swapping your sentences round...

I often use the index inside the for loop for various things such as case statements...

Which is why the control variable has an associated name, which has been accepted practice since e.g. early FORTRAN:

Code: Fortran  [Select][+][-]
  1.       do 20 i = 10, 1, -2
  2.          write(*,*) 'i =', i
  3. 20  continue
  4.  

There are other ways of doing it e.g. by iterating over an ordered collection, but if you want your control variable to be accessible to e.g. index into an array or be an operand in an arithmetic expression having it as a variable is pretty standard: I think that pretty much all of us who've done some "under the hood" compiler work have tried alterbatives and found them lacking.

Quote
I didn’t know that a for loop was a cpu register I always imagined that it was just the variable being incremented automatically.

Right. I don't want to sound condescending here, but you have said that you know little if anything about compilers so let's see whether we can throw some light on things.

When you're inside a function (aka procedure in Pascal terms) variables get stored on the stack. If you consider a simple CPU with only a couple of general-purpose registers you should be able to see that the value of any variable is going to be continually shuffled back and forth between memory and register, and allowing for the gross disparity between register and RAM speed that gets to be inefficient. A relatively recent compiler looks at the control and data flows, and if a value in a register isn't needed then it won't be written back to memory.

So Wirth's dictum that the control variable has no defined value on completion of the loop doesn't necessarily mean that it /has/ to be stored in a register and never written out to a variable in memory, but it does permit the compiler to drop the value of the control variable at the end of the loop without saving it.

Whether a particular compiler, targeting a particular architecture with some number of registers, chooses to avail itself of that optimisation is an implementation issue. But it is a language definition issue that it /can/ use that optimisation, and that programmers should assume nothing else.

So assuming that the control variable is i, the value- in memory- of i on conclusion of the loop depends on whether it has ever been "spilled" from a register to memory, and that depends on whether calculations in the loop were sufficiently complex that the compiler decided that it was more efficient to spill it (and reuse the register for something else) than to keep it in a register. From the user's POV that is not easily predictable, and Wirth's language definition explicitly spares the user from making that decision.

Hope that helps.

MarkMLl

Edited: removed a second FORTRAN example which didn't make sense. Haven't touched that stuff since the 70s...
« Last Edit: August 02, 2024, 12:31:04 pm by MarkMLl »
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

MarkMLl

  • Hero Member
  • *****
  • Posts: 7725
Reading this topic, I had a thought. Should a doctor, engineer, or scientist, when working with the Pascal language (or any other language for that matter), be looking under the hood and analyzing the assembly code and optimizations? I think the user should simply follow the rules of the language and let the compiler do the rest. It's great if the programmer makes a mistake and the compiler points it out!

Should a doctor, working in the field, know how to sterilise an impromptu dressing by boiling it?

Should an engineer, making a rare trip from his office, be able to look at an assembly and say "something looks a bit off there..."?

Should a scientist, looking at a table of results, be able to say "something's skewed the probability..." without machine-readable data that he can dump into R or Mathcad?

The first thing is that you have to trust the documentation to be correct, and to have sufficient experience to be able to spot inconsistencies. That applies to all fields.

In the case of software writing, I believe that it's necessary to have sufficient understanding of the tools being used to be able to spot gross departures from the documented behaviour, and where necessary be able to capture diagnostic information (e.g. the fragment of emitted assembler that corresponds to a fragment of a submitted program) to allow problems to be pursued by "the next level of support", irrespective of whether that support is paid-for or contributed by volunteers.

Just my 2d-worth :-)

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

BrunoK

  • Hero Member
  • *****
  • Posts: 589
  • Retired programmer
Case in point: I just tested the following two equivalent loops for summing a bunch of integers (FPC trunk, x86-64, Linux, -O4 optimization):

The compiler didn't place the index variable in a register in either case.
For  loop:

Apart from a single additional  jmp  at the beginning of the  while  loop, the generated code happens to be completely identical.
Personal I do not touch O4.
My settings are -O1 -OoREGVAR that are what I found the safest, and generally fast, code generation.
Those settings result in variables much placed in registers.
Assembler window :
Code: Pascal  [Select][+][-]
  1. C:\Users\BRUNOK~1\AppData\Local\Temp\project1.lpr:9  Sum := 0;
  2. 00000001000015D4 31C9                     xor ecx,ecx
  3. C:\Users\BRUNOK~1\AppData\Local\Temp\project1.lpr:10  for I := 0 to 999 do begin
  4. 00000001000015D6 48C7C0FFFFFFFF           mov rax,$FFFFFFFF
  5. 00000001000015DD 0F1F00                   nop dword ptr [rax]
  6. 00000001000015E0 4883C001                 add rax,$01
  7. C:\Users\BRUNOK~1\AppData\Local\Temp\project1.lpr:11  Inc(Sum, I);
  8. 00000001000015E4 4801C1                   add rcx,rax
  9. C:\Users\BRUNOK~1\AppData\Local\Temp\project1.lpr:10  for I := 0 to 999 do begin
  10. 00000001000015E7 483DE7030000             cmp rax,$000003E7
  11. 00000001000015ED 7CF1                     jl -$0F    # $00000001000015E0 TestForWhile+16 project1.lpr:10
  12. C:\Users\BRUNOK~1\AppData\Local\Temp\project1.lpr:14  Sum := 0;
  13. 00000001000015EF 31D2                     xor edx,edx
  14. C:\Users\BRUNOK~1\AppData\Local\Temp\project1.lpr:15  J := 0;
  15. 00000001000015F1 31C0                     xor eax,eax
  16. C:\Users\BRUNOK~1\AppData\Local\Temp\project1.lpr:16  while J < 1000 do begin
  17. 00000001000015F3 EB0A                     jmp +$0A    # $00000001000015FF TestForWhile+47 project1.lpr:16
  18. 00000001000015F5 0F1F00                   nop dword ptr [rax]
  19. C:\Users\BRUNOK~1\AppData\Local\Temp\project1.lpr:17  Inc(Sum, J);
  20. 00000001000015F8 4801C2                   add rdx,rax
  21. C:\Users\BRUNOK~1\AppData\Local\Temp\project1.lpr:18  Inc(J);
  22. 00000001000015FB 4883C001                 add rax,$01
  23. C:\Users\BRUNOK~1\AppData\Local\Temp\project1.lpr:16  while J < 1000 do begin
  24. 00000001000015FF 483DE8030000             cmp rax,$000003E8
  25. 0000000100001605 7CF1                     jl -$0F    # $00000001000015F8 TestForWhile+40 project1.lpr:17
  26. C:\Users\BRUNOK~1\AppData\Local\Temp\project1.lpr:20  end;
  27. 0000000100001607 488D6500                 lea rsp,[rbp+$00]
  28.  
and notice also that the while loop takes less bytes than the for loop despite the while loop doing more things.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5692
  • Compiler Developer
I have previously requested that the code tools not put semicolon after the “do” But to no avail. Ridiculous code that does nothing should not even compile in my opinion... ;D It’s a big flaw in fpc to rely upon programmers to notice and remove a semicolon placed where it doesn’t belong. I have had this break my code several times. Why on earth can’t it be fixed?

It is perfectly valid Pascal code. Empty blocks are allowed and there are even use cases for it: on embedded systems one might use a for-loop without a body as a small delay (e.g. on an AVR micro controller one can estimate the amount of cycles the loop will take and thus do a more or less controlled delay without having timers available). I've used such myself already in the past. So only because you don't see a use for it, doesn't mean that there isn't one.

Nonetheless I just tested it and it gave 10 just as predicted. I’m using no optimization.

“undefined” can mean that one might see the result one desires, but it is not guaranteed. It heavily depends upon the code generated as explained time and time again by various people here already. For example depending on the loop the compiler might decide to invert it (e.g. when the index variable isn't used inside the body which can be a valid usecase) in which case the loop variable will count from 10 to 0 in this example as comparisons against 0 can be cheaper depending on the hardware.

Joanna

  • Hero Member
  • *****
  • Posts: 1105
Thanks for the explanations.
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  #pascal Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

ASerge

  • Hero Member
  • *****
  • Posts: 2325
Case in point: I just tested the following two equivalent loops for summing a bunch of integers (FPC trunk, x86-64, Linux, -O4 optimization):
...
The compiler didn't place the index variable in a register in either case.
FPC 3.2.2 x64, Windows, -O3.
All variables are in registers:
Code: ASM  [Select][+][-]
  1. .section .text.n_unit1_$$_test,"x"
  2.         .balign 16,0x90
  3. UNIT1_$$_TEST:
  4. .Lc1:
  5. # Var I located in register rax
  6. # Var J located in register rax
  7. # Var Sum located in register rax
  8. # [unit1.pp]
  9. # [31] begin
  10. # Var Sum located in register rcx
  11. .Ll1:
  12. # [33] Sum := 0;
  13.         xorl    %ecx,%ecx
  14. # Var I located in register rax
  15. .Ll2:
  16. # [34] for I := 0 to 999 do begin
  17.         movq    $-1,%rax
  18.         .balign 8,0x90
  19. .Lj5:
  20.         addq    $1,%rax
  21. .Ll3:
  22. # [35] Inc(Sum, I);
  23.         addq    %rax,%rcx
  24. .Ll4:
  25.         cmpq    $999,%rax
  26.         jnge    .Lj5
  27. # Var Sum located in register rdx
  28. .Ll5:
  29. # [38] Sum := 0;
  30.         xorl    %edx,%edx
  31. # Var J located in register rax
  32. .Ll6:
  33. # [39] J := 0;
  34.         xorl    %eax,%eax
  35. .Ll7:
  36. # [40] while J < 1000 do begin
  37.         jmp     .Lj9
  38.         .balign 8,0x90
  39. .Lj8:
  40. .Ll8:
  41. # [41] Inc(Sum, J);
  42.         addq    %rax,%rdx
  43. .Ll9:
  44. # [42] Inc(J);
  45.         addq    $1,%rax
  46. .Lj9:
  47. .Ll10:
  48.         cmpq    $1000,%rax
  49.         jl      .Lj8
  50. .Ll11:
  51. # [44] end;
  52.         ret
  53. .Lc2:
  54. .Lt1:
  55. .Ll12:

MarkMLl

  • Hero Member
  • *****
  • Posts: 7725
That would be an extension/improvement of the DFA (Data Flow Analysis). But yes, it warrants a bug report, so that especially FPK knows what's missing (as he is the one mainly working on DFA).

Has anybody raised one for this? If not I'll do it since this is an area I've been complaining about for years... decades... and it's more likely to be improved by a warning than by addition of a local variable declaration.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

jamie

  • Hero Member
  • *****
  • Posts: 6591
it would be nice to have inline loop counters that go out of scope when the loop is done.

Code: Pascal  [Select][+][-]
  1. For A:Integer = 0 to Whatever do...
  2.  
  3.  

For any other type of inlining variables I would say a no-no, I don't like how C does that, it makes for a mess, but it would be nice if Code tools would allow you to create a variable in place and have it automatically inserted in the VAR list nearest without leaving the current line in the editor.

 Maybe it does that now?


 
The only true wisdom is knowing you know nothing

LV

  • Jr. Member
  • **
  • Posts: 79
it would be nice if Code tools would allow you to create a variable in place and have it automatically inserted in the VAR list nearest without leaving the current line in the editor.

 Maybe it does that now?

Maybe Ctrl+Shift+C

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2.  
  3. var
  4.   a: Integer; // it automatically inserted in the VAR list nearest without leaving the current line in the editor
  5. begin
  6.   {Ctrl+Shift+C} a:=2;
  7. end;  
  8.  
« Last Edit: August 03, 2024, 03:51:04 pm by LV »

 

TinyPortal © 2005-2018