Generic type conflicts

classic Classic list List threaded Threaded
26 messages Options
12
Reply | Threaded
Open this post in threaded view
|

Generic type conflicts

Free Pascal - General mailing list
I've wanted to make a generic version of a vector for a while but I always give up because it seems not very possible. It's probably not even a great idea because many methods don't translate between float and integer but I wanted to prevent other code duplication if possible.

Here's an example of how things break down. Are there any solutions for this currently? I feel like generics need to support some compiler directives so different blocks of code can specialize different depending on the type.

{$mode objfpc}
{$modeswitch advancedrecords}

program generic_vector_2;
uses
  Math;

type
  generic TVec2<TScalar> = record
    x, y: TScalar;
    function Normalize: TVec2;
  end;
  TVec2f = specialize TVec2<Float>;
  TVec2i = specialize TVec2<Integer>;

function TVec2.Normalize: TVec2;
var
  fac: TScalar;
begin
  // Can't determine which overloaded function to call
  // Incompatible types: got "Extended" expected "LongInt"
  fac:=Sqrt(Sqr(x) + Sqr(y));
  if fac<>0.0 then begin
    // Incompatible types: got "Single" expected "LongInt"
    fac:=1.0/fac;
    result.x:=x*fac;
    result.y:=y*fac;
  end else begin
    result.x:=0;
    result.y:=0;
  end;
end;

begin
end.

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
On 02/11/2019 16:55, Ryan Joseph via fpc-pascal wrote:

> I've wanted to make a generic version of a vector for a while but I always give up because it seems not very possible. It's probably not even a great idea because many methods don't translate between float and integer but I wanted to prevent other code duplication if possible.
>
> Here's an example of how things break down. Are there any solutions for this currently? I feel like generics need to support some compiler directives so different blocks of code can specialize different depending on the type.
>
> {$mode objfpc}
> {$modeswitch advancedrecords}
>
> program generic_vector_2;
> uses
>    Math;
>
> type
>    generic TVec2<TScalar> = record
>      x, y: TScalar;
>      function Normalize: TVec2;
>    end;
>    TVec2f = specialize TVec2<Float>;
>    TVec2i = specialize TVec2<Integer>;
>
> function TVec2.Normalize: TVec2;
> var
>    fac: TScalar;
> begin
>    // Can't determine which overloaded function to call
>    // Incompatible types: got "Extended" expected "LongInt"
>    fac:=Sqrt(Sqr(x) + Sqr(y));
>    if fac<>0.0 then begin
>      // Incompatible types: got "Single" expected "LongInt"
>      fac:=1.0/fac;
>      result.x:=x*fac;
>      result.y:=y*fac;
>    end else begin
>      result.x:=0;
>      result.y:=0;
>    end;
> end;
>
> begin
> end.
>
> Regards,
> Ryan Joseph
>
> _______________________________________________
> fpc-pascal maillist  -  [hidden email]
> https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
>

You need to do a explicit typecasting.

...
fac:=Sqrt(Sqr(TScalar(x)) + Sqr(TScalar(y)));
...
fac:=1.0/TScalar(fac);

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list


> On Nov 2, 2019, at 11:06 AM, Cyrax via fpc-pascal <[hidden email]> wrote:
>
> You need to do a explicit typecasting.
>
> ...
> fac:=Sqrt(Sqr(TScalar(x)) + Sqr(TScalar(y)));
> ...
> fac:=1.0/TScalar(fac);

Doesn't work. Try running the example.

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
In reply to this post by Free Pascal - General mailing list
On Sat, Nov 2, 2019 at 11:51 AM Ryan Joseph via fpc-pascal <[hidden email]> wrote:
Are there any solutions for this currently? I feel like generics need to support some compiler directives so different blocks of code can specialize different depending on the type.

There's one, that works *exactly* like you want (although it's not currently present in trunk FPC) which is to apply the (still-working) patch Sven posted in this mailing list thread a few years ago:

It introduces a const-evaluated "ternary if" of the form "Variable := if Expression else Expression;" where only the true branch is taken into consideration by the compiler at all.

Combining it with handy compiler intrinsics like GetTypeKind makes stuff like the following modified version of your code possible: 

{$mode objfpc}
{$modeswitch advancedrecords}

program generic_vector_2;

type
  generic TVec2<TScalar> = record
    X, Y: TScalar;
    class function Create(const AX, AY: TScalar): TVec2; static; inline;
    class operator / (const Left, Right: TVec2): TVec2; inline;
  end;

  class function TVec2.Create(const AX, AY: TScalar): TVec2;
  begin
    with Result do begin
      X := AX;
      Y := AY;
    end;
  end;

  class operator TVec2./(const Left, Right: TVec2): TVec2;
  begin
    // GetTypeKind is evaluated at compile time, so the following works perfectly with "ternary if".
    Result :=
      if GetTypeKind(TScalar) = tkFloat then
        TVec2.Create(Left.X / Right.X, Left.Y / Right.Y)
      else
        TVec2.Create(Left.X div Right.X, Left.Y div Right.Y);
  end;

type
  TVec2f = specialize TVec2<Single>;
  TVec2i = specialize TVec2<Integer>;

begin
end.

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
Ben Grasset via fpc-pascal <[hidden email]> schrieb am Di., 5. Nov. 2019, 19:11:
On Sat, Nov 2, 2019 at 11:51 AM Ryan Joseph via fpc-pascal <[hidden email]> wrote:
Are there any solutions for this currently? I feel like generics need to support some compiler directives so different blocks of code can specialize different depending on the type.

There's one, that works *exactly* like you want (although it's not currently present in trunk FPC) which is to apply the (still-working) patch Sven posted in this mailing list thread a few years ago:

It introduces a const-evaluated "ternary if" of the form "Variable := if Expression else Expression;" where only the true branch is taken into consideration by the compiler at all.

Combining it with handy compiler intrinsics like GetTypeKind makes stuff like the following modified version of your code possible: 

{$mode objfpc}
{$modeswitch advancedrecords}

program generic_vector_2;

type
  generic TVec2<TScalar> = record
    X, Y: TScalar;
    class function Create(const AX, AY: TScalar): TVec2; static; inline;
    class operator / (const Left, Right: TVec2): TVec2; inline;
  end;

  class function TVec2.Create(const AX, AY: TScalar): TVec2;
  begin
    with Result do begin
      X := AX;
      Y := AY;
    end;
  end;

  class operator TVec2./(const Left, Right: TVec2): TVec2;
  begin
    // GetTypeKind is evaluated at compile time, so the following works perfectly with "ternary if".
    Result :=
      if GetTypeKind(TScalar) = tkFloat then
        TVec2.Create(Left.X / Right.X, Left.Y / Right.Y)
      else
        TVec2.Create(Left.X div Right.X, Left.Y div Right.Y);
  end;

type
  TVec2f = specialize TVec2<Single>;
  TVec2i = specialize TVec2<Integer>;

begin
end.

Does this really work? Cause the compiler should nevertheless typecheck the code in the other branch and thus without casts that shouldn't compile. 
Also if it should indeed work, it would also work without the if-expression, but with an if-statement. 

Regards, 
Sven 

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
On Tue, Nov 5, 2019 at 5:24 PM Sven Barth via fpc-pascal <[hidden email]> wrote:
Does this really work? Cause the compiler should nevertheless typecheck the code in the other branch and thus without casts that shouldn't compile. 
Also if it should indeed work, it would also work without the if-expression, but with an if-statement.

Hm, it actually doesn't quite for that particular example, testing it for real. It definitely did for a few other generic things I experimented with though (that were impossible otherwise.) I'll see if I can find the source files anywhere.

Why would it work the same way as a normal if statement, though? Isn't the non-evaluation of the false branch pretty much the only point of the ternary "form"? E.G. you'd specifically use it when you had two branches that you knew would never *both* be compilable.
You'd be able to do the same thing with an {$IFDEF}, for example, if {$IFDEFS} specifically carried through across generic specializations.

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
Ben Grasset via fpc-pascal <[hidden email]> schrieb am Mi., 6. Nov. 2019, 04:49:
On Tue, Nov 5, 2019 at 5:24 PM Sven Barth via fpc-pascal <[hidden email]> wrote:
Does this really work? Cause the compiler should nevertheless typecheck the code in the other branch and thus without casts that shouldn't compile. 
Also if it should indeed work, it would also work without the if-expression, but with an if-statement.

Hm, it actually doesn't quite for that particular example, testing it for real. It definitely did for a few other generic things I experimented with though (that were impossible otherwise.) I'll see if I can find the source files anywhere.

Why would it work the same way as a normal if statement, though? Isn't the non-evaluation of the false branch pretty much the only point of the ternary "form"? E.G. you'd specifically use it when you had two branches that you knew would never *both* be compilable.
You'd be able to do the same thing with an {$IFDEF}, for example, if {$IFDEFS} specifically carried through across generic specializations.

A normal if-statements has the same non-evaluation. However that it might not be evaluated does not mean that it is not type checked. Especially as the type of the expression is taken from the if-branch. Imagine this:

=== code begin ===

SomeLongint := if SomethingFalse then SomeOtherLongint else SomeString;

=== code end ===

The if-branch is obviously not taken here. But nevertheless the type of the whole condition is LongInt, because the type of the if-branch is LongInt. Thus there will be a type error in the else-branch even before the compiler determines that the if-condition is constant and False. 

And the if-expression behaves like an if-clause, because the compiler transforms it as such. The above example becomes (in the node tree) essentially the following (general case without further optimizations):

=== code begin ===

if SomethingFalse then
  tmpLongInt := SomeOtherLongint
else
  tmpLongInt := SomeString;
SomeLongint := tmpLongInt;

===code end ===

Regards, 
Sven 

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
On Wed, Nov 6, 2019 at 2:01 AM Sven Barth via fpc-pascal <[hidden email]> wrote:
A normal if-statements has the same non-evaluation.

Not in the way I meant, though. 

Like, I thought the difference between "normal if" and "ternary if" was supposed to be the same as the difference between the existing IfThen() function and the intrinsic version of IfThen(), on which you based the "if-then-else" syntax I think. 

By which I mean, something similar to the difference between "if" and "static if" in the D programming language (at least when given things that are possible to evaluate at compile time, like GetTypeKind is in Pascal). For example:

import std.stdio;
import std.traits;

// Would not compile, because everything is evaluated fully,
// and in this case the parameters we pass aren't
// compatible with all branches.
void PrintSomething(T)(T value) {
  if (isFloatingPoint!(T)) {
    writefln("Floating point value: %f", value * value);
  } else if (isIntegral!(T)) {
    writefln("Integral value: %d", value * value);
  } else if (isSomeString!(T)) {
    writefln("String value: %s", value ~ value);
  }
}

// Compiles fine, as only the relevant branch for
// a given parameter is evaluated.
void StaticPrintSomething(T)(T value) {
  static if (isFloatingPoint!(T)) {
    writefln("Floating point value: %f", value * value);
  }
  else static if (isIntegral!(T)) {
    writefln("Integral value: %d", value * value);
  }
  else static if (isSomeString!(T)) {
    // "~" does string concatenation in D
    writefln("String value: %s", value ~ value ~ value);
  }
}

void main() {
  StaticPrintSomething(1);
  StaticPrintSomething(1.0);
  StaticPrintSomething("hey");
}

The point being that the type-checking is neither useful or necessary in scenarios where the branch being evaluated is statically known to be unreachable ahead of time.

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
Ben Grasset via fpc-pascal <[hidden email]> schrieb am Mi., 6. Nov. 2019, 15:43:
On Wed, Nov 6, 2019 at 2:01 AM Sven Barth via fpc-pascal <[hidden email]> wrote:
A normal if-statements has the same non-evaluation.

Not in the way I meant, though. 

Like, I thought the difference between "normal if" and "ternary if" was supposed to be the same as the difference between the existing IfThen() function and the intrinsic version of IfThen(), on which you based the "if-then-else" syntax I think. 

No. The difference between the currently provided IfThen<> generic is the same in regards to evaluation to the rejected IfThen intrinsic, an if-expression and an if-statement: All four typecheck their branches, but only the later three don't execute the statements in the branch that is not taken (and also have a branch optimised away if the condition is const).
The only difference between the if-expression and the if-statement is that the former can be used as an expression (duh!). 

The point being that the type-checking is neither useful or necessary in scenarios where the branch being evaluated is statically known to be unreachable ahead of time.

Pascal has a strong type safety, thus something like the if-expression won't be used/allowed to weaken that. 

If that means that some things can't be implemented in generics the "easy" way, then so be it. 

Regards, 
Sven 

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
On Wed, Nov 6, 2019 at 12:44 PM Sven Barth via fpc-pascal <[hidden email]> wrote:
Pascal has a strong type safety, thus something like the if-expression won't be used/allowed to weaken that. 

If that means that some things can't be implemented in generics the "easy" way, then so be it.

I agree that Pascal has strong type safety.

IMO conditional branching based on compile-time constant evaluation, that cannot fail, does not do anything resembling weakening it, however. 

It strengthens it, and has no downsides, because it's verified by the compiler.

Encouraging typecasting (which cares only about the sizes of the types involved, nothing else) at the programmer level is far more error-prone in a variety of ways.

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
On Wed, Nov 6, 2019 at 7:33 PM Ben Grasset <[hidden email]> wrote:
Encouraging typecasting (which cares only about the sizes of the types involved, nothing else) at the programmer level is far more error-prone in a variety of ways.

Also: it's slower in many cases, because it tends to involve "if" statements that *remain* as if statements in the final generated assembly code, whereas a static check would allow for simply generating *only* the code for the path that's actually taken.

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
Am 07.11.2019 um 01:42 schrieb Ben Grasset via fpc-pascal:
On Wed, Nov 6, 2019 at 7:33 PM Ben Grasset <[hidden email]> wrote:
Encouraging typecasting (which cares only about the sizes of the types involved, nothing else) at the programmer level is far more error-prone in a variety of ways.

Also: it's slower in many cases, because it tends to involve "if" statements that *remain* as if statements in the final generated assembly code, whereas a static check would allow for simply generating *only* the code for the path that's actually taken.
This is not true. If the compiler can prove at compile time that the if-condition is constant then the branch that is not taken is removed at the node level. It doesn't even remotely reach the code generation phase. (I just checked that yesterday when I implemented IsManagedType() and wondered about missing "unreachable code" warnings).

Take a look at the test for IsManagedType():

=== code begin ===

program tismngd1;

{$mode objfpc}
{$modeswitch advancedrecords}

uses
  TypInfo;

var
  gError: LongInt = 0;

function NextErrorCode: LongInt; inline;
begin
  Inc(gError);
  Result := gError;
end;

generic procedure TestType<T>(aIsMngd: Boolean); inline;
begin
  if IsManagedType(T) <> aIsMngd then begin
    Writeln('IsManagedType(', PTypeInfo(TypeInfo(T))^.Name, ') failure; expected: ', aIsMngd, ', got: ', IsManagedType(T));
    Halt(NextErrorCode);
  end;
  NextErrorCode;
end;

type
  TTestLongInt = record
    a: LongInt;
  end;

  TTestAnsiString = record
    a: AnsiString;
  end;

  TTestManaged = record
    a: LongInt;
    class operator Initialize(var aTestManaged: TTestManaged);
  end;

  TTestObj = object
    a: LongInt;
  end;

  TTestObjAnsiString = object
    a: AnsiString;
  end;

class operator TTestManaged.Initialize(var aTestManaged: TTestManaged);
begin
  aTestManaged.a := 42;
end;

type
  TProcVar = procedure;
  TMethodVar = procedure of object;

  TDynArrayLongInt = array of LongInt;
  TStaticArrayLongInt = array[0..4] of LongInt;
  TStaticArrayAnsiString = array[0..4] of AnsiString;

  TEnum = (eOne, eTwo, eThree);
  TSet = set of (sOne, sTwo, sThree);

begin
  specialize TestType<LongInt>(False);
  specialize TestType<Boolean>(False);
  specialize TestType<ShortString>(False);
  specialize TestType<AnsiString>(True);
  specialize TestType<UnicodeString>(True);
  specialize TestType<WideString>(True);
  specialize TestType<Single>(False);
  specialize TestType<TProcVar>(False);
  specialize TestType<TMethodVar>(False);
  specialize TestType<Pointer>(False);
  specialize TestType<IInterface>(True);
  specialize TestType<TObject>(False);
  specialize TestType<TTestLongInt>(False);
  specialize TestType<TTestAnsiString>(True);
  specialize TestType<TTestManaged>(True);
  specialize TestType<TTestObj>(False);
  specialize TestType<TTestObjAnsiString>(True);
  specialize TestType<TDynArrayLongInt>(True);
  specialize TestType<TStaticArrayLongInt>(False);
  specialize TestType<TStaticArrayAnsiString>(True);
  specialize TestType<TEnum>(False);
  specialize TestType<TSet>(False);
  Writeln('Ok');
end.

=== code end ===

Thanks to the node level optimization I mentioned the assembly code of the main function will look like this (in this case x86_64-win64):

=== code begin ===

.section .text.n_main,"ax"
    .balign 16,0x90
.globl    main
main:
.globl    PASCALMAIN
PASCALMAIN:
.Lc122:
.Lc123:
# Temps allocated between rbp-8 and rbp+0
.seh_proc main
# [62] begin
    pushq    %rbp
.seh_pushreg %rbp
.Lc124:
.Lc125:
    movq    %rsp,%rbp
.Lc126:
    leaq    -48(%rsp),%rsp
.seh_stackalloc 48
    movq    %rbx,-8(%rbp)
.seh_savereg %rbx, 40
.seh_endprologue
    call    fpc_initializeunits
# [63] specialize TestType<LongInt>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [64] specialize TestType<Boolean>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [65] specialize TestType<ShortString>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [66] specialize TestType<AnsiString>(True);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [67] specialize TestType<UnicodeString>(True);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [68] specialize TestType<WideString>(True);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [69] specialize TestType<Single>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [70] specialize TestType<TProcVar>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [71] specialize TestType<TMethodVar>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [72] specialize TestType<Pointer>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [73] specialize TestType<IInterface>(True);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [74] specialize TestType<TObject>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [75] specialize TestType<TTestLongInt>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [76] specialize TestType<TTestAnsiString>(True);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [77] specialize TestType<TTestManaged>(True);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [78] specialize TestType<TTestObj>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [79] specialize TestType<TTestObjAnsiString>(True);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [80] specialize TestType<TDynArrayLongInt>(True);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [81] specialize TestType<TStaticArrayLongInt>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [82] specialize TestType<TStaticArrayAnsiString>(True);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [83] specialize TestType<TEnum>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [84] specialize TestType<TSet>(False);
    addl    $1,TC_$P$TISMNGD1_$$_GERROR(%rip)
    movl    TC_$P$TISMNGD1_$$_GERROR(%rip),%eax
# [85] Writeln('Ok');
    call    fpc_get_output
    movq    %rax,%rbx
    leaq    _$TISMNGD1$_Ld3(%rip),%r8
    movq    %rbx,%rdx
    movl    $0,%ecx
    call    fpc_write_text_shortstr
    call    fpc_iocheck
    movq    %rbx,%rcx
    call    fpc_writeln_end
    call    fpc_iocheck
# [86] end.
    call    fpc_do_exit
    movq    -8(%rbp),%rbx
    leaq    (%rbp),%rsp
    popq    %rbp
    ret
.seh_endproc
.Lc121:

=== code end ===

As you can see the compiler basically optimized everything except the increment of the gError variable away, because it determined it can do so. And this was *without* any explicit optimizations enabled.

Regards,
Sven

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
In reply to this post by Free Pascal - General mailing list
Am 07.11.2019 um 01:33 schrieb Ben Grasset via fpc-pascal:
On Wed, Nov 6, 2019 at 12:44 PM Sven Barth via fpc-pascal <[hidden email]> wrote:
Pascal has a strong type safety, thus something like the if-expression won't be used/allowed to weaken that. 

If that means that some things can't be implemented in generics the "easy" way, then so be it.

I agree that Pascal has strong type safety.

IMO conditional branching based on compile-time constant evaluation, that cannot fail, does not do anything resembling weakening it, however. 

It strengthens it, and has no downsides, because it's verified by the compiler.

Encouraging typecasting (which cares only about the sizes of the types involved, nothing else) at the programmer level is far more error-prone in a variety of ways.
If there is no type checking, then it is *not* verified by the compiler. If I have an if-expression with an always true if-clause I could write any syntactically correct garbage in the else-clause if type checking would be disabled, because the compiler would not verify it. And this is not how Pascal works. And also not how FPC's parser works. We're taking huge care that generics are correctly type checked and that as many errors as possible are caught when writing the generic (instead of when specializing), so we're not going to introduce something like this. The if-expression is intended to be like C/C++'s ternary operator nothing more, nothing less cause that is what most people want to use it for.

Regards,
Sven

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
On Thu, Nov 7, 2019 at 3:03 AM Sven Barth via fpc-pascal <[hidden email]> wrote:
If there is no type checking, then it is *not* verified by the compiler.

Perhaps "no type checking" was not the write way to put it. A better way to describe it might be:

Since the compiler *always* knows exactly which type a generic type constraint currently amounts to, it is clearly capable of properly choosing specific code paths based on that, if given some kind of constant-evaluatable boolean condition that is specifically based on TTypeKind.

In general, it's *exactly* the same concept as something like using SizeOf in an {$IF} / {$ELSEIF} directive pair (where the compiler does indeed completely ignore everything in the "false" section):

program Example;

begin
  // You can in fact put stuff that isn't even close to valid code in whichever block is false for you, below.
  {$IF SizeOf(Pointer) = 4}
    WriteLn('The size of a pointer is 4 bytes.');
  {$ELSEIF SizeOf(Pointer) = 8}
    WriteLn('The size of a pointer is 8 bytes.');
  {$ENDIF}
end.

Not that it has to do precisely the same thing for a hypothetical TTypeKind-based choice. 

E.G. it could still do the full checking, as the only thing that really matters is that it does not actively raise error messages only relevant for Integer under code blocks only entered for tkFloat.

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Michael Van Canneyt


On Thu, 7 Nov 2019, Ben Grasset via fpc-pascal wrote:

> On Thu, Nov 7, 2019 at 3:03 AM Sven Barth via fpc-pascal <
> [hidden email]> wrote:
>
>> If there is no type checking, then it is *not* verified by the compiler.
>>
>
> Perhaps "no type checking" was not the write way to put it. A better way to
> describe it might be:
>

[snip]

> E.G. it could still do the full checking, as the only thing that really
> matters is that it does not actively raise error messages only relevant for
> Integer under code blocks only entered for tkFloat.

If I understood Sven's example correct, then the compiler does exactly this
already.

Michael.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
Michael Van Canneyt <[hidden email]> schrieb am Do., 7. Nov. 2019, 16:23:


On Thu, 7 Nov 2019, Ben Grasset via fpc-pascal wrote:

> On Thu, Nov 7, 2019 at 3:03 AM Sven Barth via fpc-pascal <
> [hidden email]> wrote:
>
>> If there is no type checking, then it is *not* verified by the compiler.
>>
>
> Perhaps "no type checking" was not the write way to put it. A better way to
> describe it might be:
>

[snip]

> E.G. it could still do the full checking, as the only thing that really
> matters is that it does not actively raise error messages only relevant for
> Integer under code blocks only entered for tkFloat.

If I understood Sven's example correct, then the compiler does exactly this
already.

Not quite. If the generic parameter T is a LongInt, then the branch for tkFloat *must* also be valid for LongInts. Because the type checking is done *before* the branch is discarded. 

Regards, 
Sven 

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
In reply to this post by Michael Van Canneyt
On Thu, Nov 7, 2019 at 10:23 AM Michael Van Canneyt <[hidden email]> wrote:
If I understood Sven's example correct, then the compiler does exactly this
already.

It does in the sense of *code generation* for things that are specifically compiler intrinsics (which I was aware of), but not in a sense that makes the issue Ryan was posting about here avoidable. Maybe this will explain what I'm trying to get at a bit better:

program Example;

{$mode ObjFPC}

// Without actually specializing and using GDiv,
// this program compiles fine.

generic function GDiv<T>(const A, B: T): T; inline;
begin
  if GetTypeKind(T) in [tkInteger, tkInt64, tkQWord] then
    Result := A div B
  else if GetTypeKind(T) = tkFloat then
    Result := A / B
  else
    Result := Default(T);
end;

// However, once we do specialize it...

procedure UseGDiv;
begin
  // Example.pas(13,17) Error: Incompatible types: got "Double" expected "Int64"
  WriteLn(specialize GDiv<Int64>(1, 2));
  // Example.pas(11,17) Error: Operator is not overloaded: "Double" div "Double"
  WriteLn(specialize GDiv<Double>(1, 2));
end;

// Note: I'm fully aware that having the *normal* if-statement work in such a way that the
// the above would compile is neither feasible or a good idea. However, imagine something
// like the following hypothetical version of GDiv, which would rely on the conditional
// compilation aspect of the scanner to be aware of more than it currently is, and also
// for the conditional compilation syntax to be somewhat more advanced:

generic function GDiv<T>(const A, B: T): T; inline;
begin
  // Here, we're doing precisely the same kind of thing that is currently
  // possible with SizeOf, Declared, Defined, and so on.
  {$IF GetTypeKind(T) in [tkInteger, tkInt64, tkQWord]}
    Result := A div B
  {$ELSEIF GetTypeKind(T) = tkFloat}
    Result := A / B
  {$ELSE}
    Result := Default(T);
  {$ENDIF}
end;

begin
end.

Of course, implementing that kind of thing in the scanner would likely be far more difficult than implementing it as something that happens in "normal" code, via some syntax or perhaps intrinsic that makes it distinctively a fully-statically-considered conditional, as opposed to a partially-statically considered one as is the case with normal "if".

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Michael Van Canneyt


On Thu, 7 Nov 2019, Ben Grasset via fpc-pascal wrote:

> On Thu, Nov 7, 2019 at 10:23 AM Michael Van Canneyt <[hidden email]>
> wrote:
>
>> If I understood Sven's example correct, then the compiler does exactly this
>> already.
>>
>
> It does in the sense of *code generation* for things that are specifically
> compiler intrinsics (which I was aware of), but not in a sense that makes
> the issue Ryan was posting about here avoidable. Maybe this will explain
> what I'm trying to get at a bit better:
>
> program Example;
>
> {$mode ObjFPC}
>
> // Without actually specializing and using GDiv,
> // this program compiles fine.
>
> generic function GDiv<T>(const A, B: T): T; inline;
> begin
>  if GetTypeKind(T) in [tkInteger, tkInt64, tkQWord] then
>    Result := A div B
>  else if GetTypeKind(T) = tkFloat then
>    Result := A / B
>  else
>    Result := Default(T);
> end;
>
> // However, once we do specialize it...
>
> procedure UseGDiv;
> begin
>  // Example.pas(13,17) Error: Incompatible types: got "Double" expected
> "Int64"
>  WriteLn(specialize GDiv<Int64>(1, 2));
>  // Example.pas(11,17) Error: Operator is not overloaded: "Double" div
> "Double"
>  WriteLn(specialize GDiv<Double>(1, 2));
> end;

Thanks for the explanation. All is clear.

As an aside:
In my opinion (keep in mind I am not a big fan of generics) the above code
would be a prime example where one should not be using generics, but simple overloads.
If you need to use GetTypeKind in a generic, I think you're on the wrong path.
Use of IsManagedType() in a generic is stretching it, but GetTypeKind() is over the line.

Michael.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Free Pascal - General mailing list
On Fri, Nov 8, 2019 at 1:33 AM Michael Van Canneyt <[hidden email]> wrote:
As an aside:
In my opinion (keep in mind I am not a big fan of generics) the above code
would be a prime example where one should not be using generics, but simple overloads.
If you need to use GetTypeKind in a generic, I think you're on the wrong path.
Use of IsManagedType() in a generic is stretching it, but GetTypeKind() is over the line.

I agree to an extent that it's not a *huge* problem and that there are workarounds in many cases, however there's definitely certain things that simply cannot be achieved in an equally performant way via any alternative such as normal overloading.

That said, Ryan's currently-in-limbo patch for constant generic parameters actually itself in theory already provides proper solutions for quite a few aspects of the general problem, so even as it stands right now we're at least making progress towards improving the situation I'd say.

For example, I recently did a translation of the fairly well-known "PDQSort" algorithm from C++ to Pascal:


The original C++ version used "constexpr" template parameters to determine at compile time whether or not the user-provided comparison function was one for which it should use the "branchless" version of its code for item partitioning or not, and based on that passed the boolean "true" or "false" result as another constant template parameter.

The end result of course being that the compiled code is always tuned precisely to the given comparison function, without any kind of runtime selection based on non-constant boolean function parameters (which is what I've currently had to write the "branchless" choice as, defaulting to "true" unless the user specifies otherwise.)

With Ryan's patch, I'll at least be able to write that choice as a constant parameter instead, although I'm unaware of any way to replicate actually having the compiler *make* the choice.

So perhaps what's needed is not even necessarily something like "static if" support, but rather just more compiler intrinsics along the lines of GetTypeKind / IsManagedType that evaluate different things and return constant values, which in combination with Ryan's patch will go a long way as far as increasing the granularity of control available to users of FPC as far as this kind of stuff.

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Generic type conflicts

Jonas Maebe-3
On 08/11/2019 17:02, Ben Grasset via fpc-pascal wrote:

> The original C++ version used "constexpr" template parameters to
> determine at compile time whether or not the user-provided comparison
> function was one for which it should use the "branchless" version of its
> code for item partitioning or not, and based on that passed the boolean
> "true" or "false" result as another constant template parameter.
>
> The end result of course being that the compiled code is always tuned
> precisely to the given comparison function, without any kind of runtime
> selection based on non-constant boolean function parameters (which is
> what I've currently had to write the "branchless" choice as, defaulting
> to "true" unless the user specifies otherwise.)
>
> With Ryan's patch, I'll at least be able to write that choice as a
> constant parameter instead, although I'm unaware of any way to replicate
> actually having the compiler *make* the choice.

You can't. It's the main difference between C++ templates, which is a
Turing-complete programming language, and generics, which is simply a
parametrising mechanic.


Jonas
_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
12