### Bookstore

 Computer Math and Games in Pascal (preview) Lazarus Handbook

### Author Topic: Is it possible to pass an intrinsic function as a parameter?  (Read 1151 times)

#### glorfin

• Full Member
• Posts: 109
##### Is it possible to pass an intrinsic function as a parameter?
« on: July 26, 2019, 01:37:50 am »
If I define
TConversionFunction = function(X:integer):integer;

and
MyProc(V:TIntegerArray; Func:TConversionFunction);

then I can pass any function I write.

However, if I pass an intrinsic, say, abs, I get an error:
Error: Incompatible type for arg no. 2: Got "<address of function(LongInt):LongInt;InternProc>", expected "<procedure variable type of function(LongInt):LongInt;Register>"

So, is there a way to pass such a function?

#### lucamar

• Hero Member
• Posts: 3447
##### Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #1 on: July 26, 2019, 01:48:14 am »
AFAIK not directly, but you can pass a function that itself just calls the intrinsic:
Code: Pascal  [Select][+][-]
1. function DummyAbs(X: Integer): Integer;
2. begin
3.   Result := Abs(X);
4. end;
5.
6. begin
7.   AnInt := MyProc(AnInt, @DummyAbs);
8. end.

Not the ideal soultion, I know.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!)
Lazarus/FPC 2.0.8/3.0.4 & 2.0.10/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

#### glorfin

• Full Member
• Posts: 109
##### Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #2 on: July 26, 2019, 01:53:15 am »
Thank you! Yes, I thought about it, but of course it will be quite slow.

I am jealous to Fortran 90 programmers with direct operations on arrays, and want to have something like Apply. But then efficiency is an issue.

#### lucamar

• Hero Member
• Posts: 3447
##### Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #3 on: July 26, 2019, 02:27:50 am »
Thank you! Yes, I thought about it, but of course it will be quite slow.

I am jealous to Fortran 90 programmers with direct operations on arrays, and want to have something like Apply. But then efficiency is an issue.

It won't slow you too much; after all it's just one level of indirection. It may (or not) be sped up by using:
Code: Pascal  [Select][+][-]
1. function DummyAbs(constref X: Integer): Integer; inline;
2. begin
3.   Result := Abs(X);
4. end;
If the inline works (which isn't guaranteed) the indirection will dissapear and a constref might be passed in a register, which is usually quicker than pushing it to the stack.

It depends on a lot of things (not the less on whether I'm correct ); you can but try various ways  of implementing it and keep the fastest.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!)
Lazarus/FPC 2.0.8/3.0.4 & 2.0.10/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

#### glorfin

• Full Member
• Posts: 109
##### Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #4 on: July 26, 2019, 02:39:31 am »
Of course, another solution is to keep generic Apply function for user-supplied functions, and write for intrinsics specific procedures, something like

procedure AbsArray(V:TFloatArray);

and call intrinsic directly.

I will look for possibilities tomorrow.

#### lucamar

• Hero Member
• Posts: 3447
##### Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #5 on: July 26, 2019, 02:48:57 am »
Of course, another solution is to keep generic Apply function for user-supplied functions, and write for intrinsics specific procedures, something like [...snip...]

I will look for possibilities tomorrow.

Good luck!
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!)
Lazarus/FPC 2.0.8/3.0.4 & 2.0.10/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

#### jamie

• Hero Member
• Posts: 4044
##### Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #6 on: July 26, 2019, 03:22:49 am »
There is a such thing as open arrays and overloads. the compiler handles them well.

The only true wisdom is knowing you know nothing

#### glorfin

• Full Member
• Posts: 109
##### Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #7 on: July 27, 2019, 03:22:15 am »
Finally, it looks like intrinsic functions are implemented in a special way and often inlined, so, approach of lucamar is slow.
Indeed, following program
Code: Pascal  [Select][+][-]
1. program testcalls;
2. {\$mode objfpc}{\$H+}{\$inline on}
3. uses SysUtils, DateUtils, Math;
4. type
5.   Float = single;
6.   TVector = array of Float;
7.   TTransformFunc = function(X:float):float;
8.
9.   function Seq(Lb, Ub : integer; first, increment:Float; Vector:TVector = nil):TVector;
10.   var
11.     I:integer;
12.   begin
13.     if Vector = nil then
14.       SetLength(Vector, Ub+1)
15.     else
16.       Ub := Max(Ub,High(Vector));
17.     if Lb <= Ub then
18.       Vector[Lb] := 0
19.     else
20.       Exit;
21.     for I := Lb+1 to Ub do                 // 2 cycles to avoid rounding error if
22.       Vector[I] := Vector[I-1]+increment;  //First is very large and increment small
23.     for I := Lb to Ub do
24.       Vector[I] := Vector[I]+first;
25.     Result := Vector;
26.   end;
27.
28. procedure Apply(V: TVector; Lb, Ub: integer; Func: TTransformFunc);
29. var
30.   I:integer;
31. begin
32.   Ub := max(High(V),Ub);
33.   for I := Lb to Ub do
34.     V[I] := Func(V[I]);
35. end;
36.
37. function EAbs(X:float):float; inline;
38. begin
39.   result := abs(X);
40. end;
41. procedure AbsArray(V:TVector; Lb, Ub: integer);
42. var
43.   I:integer;
44. begin
45.   Ub := max(High(V),Ub);
46.   for I := Lb to Ub do
47.     V[I] := abs(V[I]);
48. end;
49.
50. var
51.   Vec:TVector; // TVector = array of Float; Float = double
52.   I:integer;
53.   time1,time2:tdatetime;
54. begin
55.   Vec := Seq(0,256000,-3000,2); // creates array with maximal index 128000
56.   time1 := time;                // and initializes with sequence -3000, -2998 etc
57.   for I := 0 to 20 do
58.     AbsArray(Vec,0,High(Vec));
59.   time2 := time;
60.   writeln('it takes ',inttostr(millisecondsbetween(time2, time1)), ' ms. for direct call of Abs.');
61.   time1 := time;
62.   for I := 0 to 20 do
63.     Apply(Vec,0,High(Vec),@EAbs);
64.   time2 := time;
65.   writeln('it takes ',inttostr(millisecondsbetween(time2, time1)), ' ms. Apply of indirect call.');
67. end.
68.

prints:

it takes 6 ms. for direct call of Abs.
it takes 21 ms. Apply of indirect call.

If Float = double thendifference is much bigger:

it takes 7 ms. for direct call of Abs.
it takes 63 ms. Apply of indirect call.

and if Float = extended then

it takes 20 ms. for direct call of Abs.
it takes 81 ms. Apply of indirect call.

So, it looks like Apply for intrunsic functions is not a good idea.

#### lucamar

• Hero Member
• Posts: 3447
##### Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #8 on: July 27, 2019, 04:11:09 am »
Woah! That's indeed a huge difference.

I'd not have expected that but as they say: "seeing is believing". The ABS results are specially damning.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!)
Lazarus/FPC 2.0.8/3.0.4 & 2.0.10/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

#### PascalDragon

• Hero Member
• Posts: 2605
• Compiler Developer
##### Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #9 on: July 27, 2019, 10:26:29 am »
Well, there is an alternative: generics. The following example requires 3.2 or newer, but the code could be reworked to work in older versions as well (by using types instead of functions). It's the example from glorfin with my code added above the program's beginend. block.

Code: Pascal  [Select][+][-]
1. program ttestcalls;
2. {\$mode objfpc}{\$H+}{\$inline on}
4. uses SysUtils, DateUtils, Math;
5. type
6.   Float = single;
7.   TVector = array of Float;
8.   TTransformFunc = function(X:float):float;
9.
10.   function Seq(Lb, Ub : integer; first, increment:Float; Vector:TVector = nil):TVector;
11.   var
12.     I:integer;
13.   begin
14.     if Vector = nil then
15.       SetLength(Vector, Ub+1)
16.     else
17.       Ub := Max(Ub,High(Vector));
18.     if Lb <= Ub then
19.       Vector[Lb] := 0
20.     else
21.       Exit;
22.     for I := Lb+1 to Ub do                 // 2 cycles to avoid rounding error if
23.       Vector[I] := Vector[I-1]+increment;  //First is very large and increment small
24.     for I := Lb to Ub do
25.       Vector[I] := Vector[I]+first;
26.     Result := Vector;
27.   end;
28.
29. procedure Apply(V: TVector; Lb, Ub: integer; Func: TTransformFunc);
30. var
31.   I:integer;
32. begin
33.   Ub := max(High(V),Ub);
34.   for I := Lb to Ub do
35.     V[I] := Func(V[I]);
36. end;
37.
38. function EAbs(X:float):float; inline;
39. begin
40.   result := abs(X);
41. end;
42. procedure AbsArray(V:TVector; Lb, Ub: integer);
43. var
44.   I:integer;
45. begin
46.   Ub := max(High(V),Ub);
47.   for I := Lb to Ub do
48.     V[I] := abs(V[I]);
49. end;
50.
51. generic procedure ApplyFunctor<T>(V: TVector; Lb, Ub: Integer);
52. var
53.   i: Integer;
54. begin
55.   Ub := max(High(v), Ub);
56.   for i := Lb to Ub do
57.     V[i] := T.Call(V[i]);
58. end;
59.
60. type
61.   TAbsFunctor = record
62.     class function Call(aValue: Float): Float; static; inline;
63.   end;
64.
65. class function TAbsFunctor.Call(aValue: Float): Float;
66. begin
67.   Result := Abs(aValue);
68. end;
69.
70. var
71.   Vec:TVector; // TVector = array of Float; Float = double
72.   I:integer;
73.   time1,time2:tdatetime;
74. begin
75.   Vec := Seq(0,256000,-3000,2); // creates array with maximal index 128000
76.   time1 := time;                // and initializes with sequence -3000, -2998 etc
77.   for I := 0 to 20 do
78.     AbsArray(Vec,0,High(Vec));
79.   time2 := time;
80.   writeln('it takes ',inttostr(millisecondsbetween(time2, time1)), ' ms. for direct call of Abs.');
81.   time1 := time;
82.   for I := 0 to 20 do
83.     Apply(Vec,0,High(Vec),@EAbs);
84.   time2 := time;
85.   writeln('it takes ',inttostr(millisecondsbetween(time2, time1)), ' ms. Apply of indirect call.');
86.   time1 := time;
87.   for i := 0 to 20 do
88.     specialize ApplyFunctor<TAbsFunctor>(Vec,0,High(Vec));
89.   time2 := time;
90.   writeln('it takes ',inttostr(millisecondsbetween(time2, time1)), ' ms. Apply of functor.');
92. end.

The output then is this:
Code: [Select]
`it takes 10 ms. for direct call of Abs.it takes 22 ms. Apply of indirect call.it takes 10 ms. Apply of functor.`Note: the first and third time varies between 10 and 12 ms for both entries.

And the generated assembly for the call to T.Call in ApplyFunctor<TAbsFunctor> is this:
Code: [Select]
`# [57] V[i] := T.Call(V[i]); movq -8(%rbp),%rax movslq -28(%rbp),%rdx leaq (%rax,%rdx,4),%rax movss (%rax),%xmm0 andps FPC_ABSMASK_SINGLE(%rip),%xmm0 movq -8(%rbp),%rdx movslq -28(%rbp),%rax movss %xmm0,(%rdx,%rax,4) cmpl -28(%rbp),%ecx jle .Lj90 jmp .Lj88`Also please note that this is not Delphi compatible as their generic implementation is less like the C++ one and more like the C# one (it will work in mode Delphi however ).

• Hero Member
• Posts: 10684
##### Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #10 on: July 27, 2019, 11:35:04 am »
I have this as an alternative abs{()
Code: Pascal  [Select][+][-]
1. {\$mode objfpc}
2. function abs2(const v:integer):integer;inline;
3. begin
4.   Result := v * -integer(v<0); // computes sign
5. end;
6.
7. begin
8.   writeln(abs2(-2));
9. end.
« Last Edit: July 27, 2019, 11:50:43 am by Thaddy »

#### LemonParty

• New Member
• Posts: 40
##### Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #11 on: July 27, 2019, 12:02:26 pm »
This is a professional https://github.com/mikerabat/mrmath method about gaining the real performance.
Nor generics, nor instrincs, nor even the best compilers can't do the high quality vectorization today.