Recent

Author Topic: Parameter passing oddities  (Read 2540 times)

Nitorami

  • Hero Member
  • *****
  • Posts: 505
Parameter passing oddities
« on: April 23, 2024, 03:31:11 pm »
I like Pascal, and it is in fact the only language I'm thoroughly familiar with. But sometimes I wonder how it seems to make simple tasks more difficult than they ought to be. Here is an example, taken from my unit for complex numbers. Don't get me wrong, the unit works and served me well over the years, but there are formal deficiencies which I struggle to resolve when aiming for effective code and a straightforward syntax.
We have

Code: Pascal  [Select][+][-]
  1. type complex = record re,im: double; end; // in fact I use advanced records but irrelevant here
  2.  
  3. procedure Add (const a,b: complex; out c: complex); inline;
  4. begin
  5.   ...
  6. end;
  7.  
  8. operator + (const a,b: complex): complex; inline;
  9. begin
  10.   Add (a,b,result);
  11. end;
  12.  

I chose this because operators, while convenient, are much slower; the infix notation requires the compiler to create a temporary and then copy it. Unfortunately the compiler uses the slow REP MOVSL for copying, as soon as the block size is larger than 8 bytes. For timing measurements see https://forum.lazarus.freepascal.org/index.php/topic,67003.msg514679.html#msg514679

Thus if speed matters, I can use the procedural version. But nevermind, that is not the issue. What concerns me is that calls like

Code: Pascal  [Select][+][-]
  1. C1 := C1+C2;
  2. add (C1,C2,C1);
break the "const" contract; C1 will be changed although declared as const. It works, the compiler does not care, but it gives me doubts. This is not clean.

So why don't I change "const" to "var":

Code: Pascal  [Select][+][-]
  1. procedure Add (var a,b: complex; out c: complex);

Fine. But - Ah ! 

Code: Pascal  [Select][+][-]
  1. Add(C1, Foo(), C2); //Error: can't take the address of constant expressions

Scratch my head. Oh yes. Understood. That does not work with functions as arguments.

Well, then let's pass by value instead. I suspect this may be a bit slower because the compiler now has to make copies of a and b, presumably using the slow REP MOVSL again. But let's try:

Code: Pascal  [Select][+][-]
  1. procedure Add (a,b: complex; out c: complex);

WTF, this is FACTOR 50 slower! Impressive, REP MOVSL hitting hard. Forget it!

Let's go back to the "const" version which is the only one that works. To prevent const parameters from being changed, we could check for identical addresses in the argument:

Code: Pascal  [Select][+][-]
  1. procedure Add (const a,b: complex; out c: complex);
  2. begin
  3.   assert ((@a<>@c) and (@b<>@c);
  4.   (....);
  5. end;
  6.  

Nice, and less than 10% overhead for checking the assertion. That should be bulletproof.
BUT - Ah!- the assertion fails on "add (C1,C2,C1)" or "C1 := C1+C2". Yes, I see. Of course it does. But I need such calls.

What shall I do ? I went back to the original version, ignoring that the const parameters may in fact change, and hoping that the compiler will not stumble across it one day.

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2269
  • Fifty shades of code.
    • Delphi & FreePascal
Re: Parameter passing oddities
« Reply #1 on: April 23, 2024, 04:15:23 pm »
Each argument is treated on its own.
To make it more simple with same meaning:
Code: Pascal  [Select][+][-]
  1. procedure Add(const a, b: Integer; out c: Integer); inline;
  2. begin
  3.   c := a + b;
  4. end;
Within the codeblock, the used keyword is law for earch argument.
So you could not a := b + c; since that would break the law.
If on the other hand you as developer call such method in a (a, b, a) way, then its upon you to decide if its smart or not.
If variable "c" is instantated as a "const", than that method would refuse it since it must be var/out and not const.
{$WRITEABLECONST OFF} would also not help to prevent a (a, b, a) usage since it has nothing to do with it.
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

alpine

  • Hero Member
  • *****
  • Posts: 1111
Re: Parameter passing oddities
« Reply #2 on: April 23, 2024, 04:47:42 pm »
Quote
BUT - Ah!- the assertion fails on "add (C1,C2,C1)" or "C1 := C1+C2". Yes, I see. Of course it does. But I need such calls.
You can define an overload, e.g.
Code: Pascal  [Select][+][-]
  1. procedure Add (var a: complex; const b: complex); overload; inline;
  2. begin
  3.   {a += b }
  4. end;
(or whatever order of parameters suits you)
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Thaddy

  • Hero Member
  • *****
  • Posts: 14647
  • Sensorship about opinions does not belong here.
Re: Parameter passing oddities
« Reply #3 on: April 23, 2024, 05:19:18 pm »
Quote
BUT - Ah!- the assertion fails on "add (C1,C2,C1)" or "C1 := C1+C2". Yes, I see. Of course it does. But I need such calls.
You can define an overload, e.g.
Code: Pascal  [Select][+][-]
  1. procedure Add (var a: complex; const b: complex); overload; inline;
  2. begin
  3.   {a += b }
  4. end;
(or whatever order of parameters suits you)
Although this is possible I would never use a var parameter, since part of your code may rely on a known value and var will change that value globally..... Not recommended.
« Last Edit: April 23, 2024, 05:32:49 pm by Thaddy »
bitrate is always calculated like this:sample rate * bitdepth * number of channels.

Nitorami

  • Hero Member
  • *****
  • Posts: 505
Re: Parameter passing oddities
« Reply #4 on: April 23, 2024, 05:24:55 pm »
Well, I could produce a shitload of overloaded versions including their respective operators. Not sure if I want that, it requires either to duplicate the code or mutual calls with intermediate tmp version. Like this

Code: Pascal  [Select][+][-]
  1. (A)
  2. procedure add (const a,b: complex; out c: complex); overload;
  3. begin
  4.   c.re := a.re+b.re;
  5.   c.im := a.im+b.im;
  6. end;
  7.  
  8. (B1)
  9. procedure add (var a: complex; const b: complex); overload; //effectively duplicating the above version
  10. begin
  11.   a.re := a.re+b.re;
  12.   a.im := a.im+b.im;
  13. end;
  14.  
  15. (B2, Alternative)
  16. procedure add (var a: complex; const b: complex); overload; //no duplicating but requiring a tmp var
  17. var tmp: complex;
  18. begin
  19.   tmp.re := a.re; //make a copy avoiding tmp := a
  20.   tmp.im := a.im;
  21.   add (tmp,b,a);
  22. end;

But all that would not be necessary if there was a method of passing parameters which works like const but avoids the "constant" assumption. I don't know how other languages handle this. Just feel something is missing in Pascal.

Thaddy

  • Hero Member
  • *****
  • Posts: 14647
  • Sensorship about opinions does not belong here.
Re: Parameter passing oddities
« Reply #5 on: April 23, 2024, 05:35:41 pm »
Those vars hurt my eyes. You have to present it better and with a proper use case.
And with a language example in a different language. I am sure fpc already has a way of expressing that. (without the in this case code mutilating var)
« Last Edit: April 23, 2024, 05:37:25 pm by Thaddy »
bitrate is always calculated like this:sample rate * bitdepth * number of channels.

alpine

  • Hero Member
  • *****
  • Posts: 1111
Re: Parameter passing oddities
« Reply #6 on: April 23, 2024, 06:16:58 pm »
Quote
But all that would not be necessary if there was a method of passing parameters which works like const but avoids the "constant" assumption. I don't know how other languages handle this. Just feel something is missing in Pascal.
I know only two methods: by-value and by-reference (without involving pointers, of course). AFAIK const parameters is to retain by-value semantics with an additional twist:
Quote
Declaring a parameter as const allows the compiler the possibility to do optimizations it couldn't do otherwise, such as passing by reference while retaining the semantics of passing by value.
   
Passing by-ref are var and constref.
What do you mean by "if there was a method of passing parameters which works like const..."?
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

ASerge

  • Hero Member
  • *****
  • Posts: 2263
Re: Parameter passing oddities
« Reply #7 on: April 23, 2024, 06:59:05 pm »
break the "const" contract; C1 will be changed although declared as const. It works, the compiler does not care, but it gives me doubts. This is not clean.
The "const" contract is valid only within the procedure.
Quote
Well, then let's pass by value instead...
WTF, this is FACTOR 50 slower! Impressive, REP MOVSL hitting hard. Forget it!
...
we could check for identical addresses in the argument...Nice, and less than 10% overhead for checking the assertion...the assertion fails...But I need such calls.
...
What shall I do ? I went back to the original version, ignoring that the const parameters may in fact change, and hoping that the compiler will not stumble across it one day.
Check without assertion:
Code: Pascal  [Select][+][-]
  1. {$APPTYPE CONSOLE}
  2. {$MODE OBJFPC}
  3.  
  4. type
  5.   TComplex = record
  6.     Im, Re: ValReal;
  7.   end;
  8.  
  9. procedure AddNotSafe(constref A, B: TComplex; out C: TComplex);
  10. begin
  11.   begin // Just for an example, in this case, the extra code
  12.     C.Im := 0; // Sloppy code that does not consider the possibility of parameter identity
  13.     C.Re := 0;
  14.   end;
  15.   C.Im := A.Im + B.Im;
  16.   C.Re := A.Re + B.Re;
  17. end;
  18.  
  19. procedure AddSlow(A, B: TComplex; out C: TComplex);
  20. begin
  21.   begin // Just for an example, in this case, the extra code
  22.     C.Im := 0; // In this case safe, becase full copy, but slow
  23.     C.Re := 0;
  24.   end;
  25.   C.Im := A.Im + B.Im;
  26.   C.Re := A.Re + B.Re;
  27. end;
  28.  
  29. procedure AddSafe(constref A, B: TComplex; out C: TComplex);
  30. begin
  31.   begin // Just for an example, in this case, the extra code
  32.     if (@A <> @C) and (@B <> @C) then // Smart code
  33.     begin
  34.       C.Im := 0;
  35.       C.Re := 0;
  36.     end;
  37.   end;
  38.   C.Im := A.Im + B.Im;
  39.   C.Re := A.Re + B.Re;
  40. end;
  41.  
  42. procedure Print(const C: TComplex);
  43. begin
  44.   Writeln('Im: ', C.Im:0:2, ', Re: ', C.Re:0:2);
  45. end;
  46.  
  47. procedure Test;
  48. var
  49.   A: TComplex = (Im:2; Re:2);
  50.   B: TComplex = (Im:1; Re:1);
  51.   C: TComplex;
  52. begin
  53.   Writeln('AddNotSafe:');
  54.   C := A;
  55.   AddNotSafe(C, B, C);
  56.   Print(C);
  57.   Writeln('AddSlow:');
  58.   C := A;
  59.   AddSlow(C, B, C);
  60.   Print(C);
  61.   Writeln('AddSafe:');
  62.   C := A;
  63.   AddSafe(C, B, C);
  64.   Print(C);
  65. end;
  66.  
  67. begin
  68.   Test;
  69.   Readln;
  70. end.

Nitorami

  • Hero Member
  • *****
  • Posts: 505
Re: Parameter passing oddities
« Reply #8 on: April 23, 2024, 09:14:49 pm »
Sorry if I did not bring my point across properly.
This
Code: Pascal  [Select][+][-]
  1. procedure add (const a,b: complex; out c: complex);
is the only version which is both fast (no copies required), and works with variables as well as function results, e.g. as in add (foo(),C1,C3);

Ok and works, BUT the "const" tells the compiler that a and b will not be changed, while when calling add (C1,C2,C1), "a" WILL be changed.

That is all, and this is what I meant with a calling method which works like "const" or "constref" BUT does not imply the "constness" of variables. The other options are "var" (but does not work with function results which have no address), and passing by value (out of the question because it is abysmally slow). And constref is essentially the same as const in this context.

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2269
  • Fifty shades of code.
    • Delphi & FreePascal
Re: Parameter passing oddities
« Reply #9 on: April 23, 2024, 09:39:22 pm »
And constref is essentially the same as const in this context.
Code: Pascal  [Select][+][-]
  1. procedure Add(constref a, b: Integer; out c: Integer);
  2. begin
  3.   if ((@a = @c) or (@b = @c)) then
  4.     begin
  5.       WriteLn('Output can not be input.');
  6.       Exit;
  7.     end;
  8.   c := a + b;
  9. end;
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

ASerge

  • Hero Member
  • *****
  • Posts: 2263
Re: Parameter passing oddities
« Reply #10 on: April 23, 2024, 09:42:03 pm »
And constref is essentially the same as const in this context.
Code: Pascal  [Select][+][-]
  1. procedure Add(constref a, b: Integer; out c: Integer);
  2. begin
  3.   if ((@a = @c) or (@b = @c)) then
  4.     begin
  5.       WriteLn('Output can not be input.');
  6.       Exit;
  7.     end;
  8.   c := a + b;
  9. end;
As I understand it, he wants a compilation error, not runtime.

Nitorami

  • Hero Member
  • *****
  • Posts: 505
Re: Parameter passing oddities
« Reply #11 on: April 23, 2024, 10:12:36 pm »
Kodezwergs check protects the "constref" declaration by prohibiting Add (C1,C2,C1).

WITHOUT this check, Add (C1,C2,C1) works, BUT formally violates the "constref" agreement.
I don't like this.

I would wish for a construct which allows Add (C1,C2,C2) as well as Add (foo(), bar(), C3) efficiently and without violating the const or constref agreement. And that does not seem to exist in Pascal.



KodeZwerg

  • Hero Member
  • *****
  • Posts: 2269
  • Fifty shades of code.
    • Delphi & FreePascal
Re: Parameter passing oddities
« Reply #12 on: April 23, 2024, 10:42:51 pm »
Kodezwergs check protects the "constref" declaration by prohibiting Add (C1,C2,C1).

WITHOUT this check, Add (C1,C2,C1) works, BUT formally violates the "constref" agreement.
I don't like this.

I would wish for a construct which allows Add (C1,C2,C2) as well as Add (foo(), bar(), C3) efficiently and without violating the const or constref agreement. And that does not seem to exist in Pascal.
Code: Pascal  [Select][+][-]
  1. procedure Add(const a, b: Integer; out c: Integer); inline;
  2. begin
  3.   c := a + b;
  4. end;
If on the other hand you as developer call such method in a (a, b, a) way, then its upon you to decide if its smart or not.

So every information about that specific topic is given.

Nothing violates, as told, you as programmer are responsable how to call methods correct.
When you call Add(a, b, a) than live with it that you overwrite "a" on purpose, within method everything works like you coded it and the variables behave like expected.
Make method a function with boolean result and constref to check addresses inside, thats how I would do to have a good workflow (if Add() then ...)
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

Nitorami

  • Hero Member
  • *****
  • Posts: 505
Re: Parameter passing oddities
« Reply #13 on: April 23, 2024, 10:48:22 pm »
Alright, agreed that I am responsible for the call, and for overwriting "a".

But the compiler expects that I don't do it and that "a" remains unchanged. Currently everything works, but can we exclude that a  future optimisation relies on "a" being unchanged ?


KodeZwerg

  • Hero Member
  • *****
  • Posts: 2269
  • Fifty shades of code.
    • Delphi & FreePascal
Re: Parameter passing oddities
« Reply #14 on: April 23, 2024, 11:04:39 pm »
Alright, agreed that I am responsible for the call, and for overwriting "a".

But the compiler expects that I don't do it and that "a" remains unchanged. Currently everything works, but can we exclude that a  future optimisation relies on "a" being unchanged ?
It seems you still have not understood yet so help me to help you.
In what line of code is "a" changed?
Code: Pascal  [Select][+][-]
  1. procedure Add(const a, b: Integer; out c: Integer); inline;
  2. begin
  3.   c := a + b;
  4. end;
So if you want that "a" stay unchanged, that you already have done since nothing write to "a", right?

You have 3 arguments, each of them can have the same reference but treated internal as unique variables where "a" does know nothing about "b" and so on.

Equal how you watch on it the result stay same, the person that uses your method needs a 1 week training to know that you as the inventor of that method wants maby 3 different arguments or 2 same or or or .... as often you change your mind what you actually want my saying stays same :D
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

 

TinyPortal © 2005-2018