Recent

Author Topic: TypeInfo() - what is it?  (Read 3179 times)

beria

  • Jr. Member
  • **
  • Posts: 70
TypeInfo() - what is it?
« on: November 30, 2022, 03:32:31 pm »
As I understood, from the point of view of the description - TypeInfo() (https://www.freepascal.org/docs-html/rtl/system/typeinfo.html) is a compiler function, which works only at compile time and returns a pointer to a variable type.  That is, it does not generate any binary code in the program. The nearest analogue of this function which works this way is IsConstValue().
But in reality a simple example shows that it is not so and it generates unnecessary sets of comparing predefined constants with predictable result...
An example for the procedure

 
Code: Pascal  [Select][+][-]
  1.  procedure test40(a: ptrUint); inline;
  2.   begin
  3.     if typeinfo(a) = typeinfo(BYTE) then Writeln('BYTE');
  4.   end;

 in the program to call test40(i), where i-ptrUint; in theory there should be no code, as, by the way, IsConstValue() works, generating code, without any conditional overflows, depending on whether the argument is a constant...
I have it generated by calling test40 ...

    ; Register edx allocated
            mov edx,dword RTTI_$SYSTEM_$$_LONGWORD
    ; Register eax,eflags allocated
            cmp edx,dword RTTI_$SYSTEM_$$_BYTE
    register eax,edx released
            jne ..@j235
    ; Register eflags released
    ; Register eax,ecx,edx allocated

Which makes no sense, because they are always compare different constants which are always predefined before the start of the program.

... Or maybe I'm missing something in the implementation of this function, and it's still not an internal compiler function, as it's written in the description, or FPC somehow allows you to change the variable's type, which is totally insane to me?

ASerge

  • Hero Member
  • *****
  • Posts: 2321
Re: TypeInfo() - what is it?
« Reply #1 on: November 30, 2022, 10:14:41 pm »
Or maybe I'm missing something in the implementation of this function, and it's still not an internal compiler function, as it's written in the description, or FPC somehow allows you to change the variable's type, which is totally insane to me?
According to the link that you yourself indicated, it is written "If no type information was yet generated for the type, this statement will ensure that type information is available, i.e. the result is always non-nil".
Thus metadata is always generated when using TypeInfo.
Use GetTypeKind:
Code: Pascal  [Select][+][-]
  1. procedure test40(a: PtrUint); inline;
  2. begin
  3.   if GetTypeKind(a) = GetTypeKind(Byte) then
  4.     Writeln('BYTE');
  5. end;

beria

  • Jr. Member
  • **
  • Posts: 70
Re: TypeInfo() - what is it?
« Reply #2 on: November 30, 2022, 11:07:43 pm »
Thank you very much. I had your option first, and it works and is very good, but it turns out that I think due to an implementation error, ALL integer types, regardless of their size in bytes, which is what I need, are the same for GetTypeKind...
The code below gives the same values.... So this built-in compiler function is a priori for me and my tasks, not working...

writeln(GetTypeKind(byte));
writeln(GetTypeKind(longint));
......

And I don't think you're right about mandatory code generation for TypeOf.... TypeOf() generates, as you can see from the code and description, just the value of the constant, like "Роinter", like IsConstValue() of the constant type "boolean" Nothing else.  And the compiler itself should, in theory, optimize them... But it always works only in the case of IsConstValue() and does not in the case of TypeOf(). I still don't know why I have to make a zoo of functions to decode a binary stream depending on the size of an integer argument in bytes instead of one universal function...

Here is a code sample optimizing constants in FPC:

Code: Pascal  [Select][+][-]
  1. const
  2.   const1 = 1;
  3.   const2 = 2;
  4.  
  5. begin
  6. if const1 = const2 then write ('test');
  7. end.

This program construction does not insert anything anywhere, neither comparing constants with predefined result, nor calling function "write"..... It should be exactly the same in theory, but it doesn't work...
« Last Edit: November 30, 2022, 11:09:19 pm by beria »

PascalDragon

  • Hero Member
  • *****
  • Posts: 5678
  • Compiler Developer
Re: TypeInfo() - what is it?
« Reply #3 on: November 30, 2022, 11:26:43 pm »
Thank you very much. I had your option first, and it works and is very good, but it turns out that I think due to an implementation error, ALL integer types, regardless of their size in bytes, which is what I need, are the same for GetTypeKind...

GetTypeKind returns a TTypeKind and for these all integer types are in fact tkInteger. They only differ by the OrdType which is part of the TTypeInfo returned by the TypeInfo intrinsic, but which isn't covered by GetTypeKind. It's intention is to optimize the main lookup of types, not all of them.

And I don't think you're right about mandatory code generation for TypeOf.... TypeOf() generates, as you can see from the code and description, just the value of the constant, like "Роinter", like IsConstValue() of the constant type "boolean" Nothing else.  And the compiler itself should, in theory, optimize them... But it always works only in the case of IsConstValue() and does not in the case of TypeOf(). I still don't know why I have to make a zoo of functions to decode a binary stream depending on the size of an integer argument in bytes instead of one universal function...

The compiler currently simply does not treat the pointers returned by TypeInfo as constant. FPC 3.3.1 however will at least optimize comparisons between two TypeInfo invocations that contain types:

Code: Pascal  [Select][+][-]
  1. if TypeInfo(PtrUInt) = TypeInfo(Byte) then Writeln('BYTE')

This will evaluate to False at compile time. I'd need to check whether it is really safe to extend this further.

beria

  • Jr. Member
  • **
  • Posts: 70
Re: TypeInfo() - what is it?
« Reply #4 on: December 01, 2022, 11:17:38 am »


Code: Pascal  [Select][+][-]
  1. if TypeInfo(PtrUInt) = TypeInfo(Byte) then Writeln('BYTE')

This will evaluate to False at compile time. I'd need to check whether it is really safe to extend this further.

I would say that the safety of such an approach follows directly from the whole paradigm of the Pascal language with a pre-description of all types.... But the procedure below is not working yet because it generates unnecessary code... By the way, for "generics" the same restrictions and code bloat.

Code: Pascal  [Select][+][-]
  1.  procedure test41(a: ptrUint); inline;
  2.   begin
  3.     if TypeInfo(a) = TypeInfo(uint8) then Writeln('1')
  4.     else if TypeInfo(a) = TypeInfo(uint16) then Writeln('2')
  5.     else if TypeInfo(a) = TypeInfo(uint32) then Writeln('4')
  6.     else if TypeInfo(a) = TypeInfo(uint64) then Writeln('8');
  7.   end;
       
ps: there is a hope in the near future to wait at least beta of new functionality, what with pleasure I will test in the real and working project?  I don't think it will be very difficult because, at first glance, I just saw how TypeInfo is implemented in the source code of FPC, just there artificially limited functionality, as opposed to many other built-in compiler functions, which also return a constant.
pps: and on a side note, is there any way for "{$mode objfpc}" to use the simpler and more obvious "generics" syntax from the Delphi compatibility relim, just that I don't use?



PascalDragon

  • Hero Member
  • *****
  • Posts: 5678
  • Compiler Developer
Re: TypeInfo() - what is it?
« Reply #5 on: December 01, 2022, 10:09:56 pm »
I would say that the safety of such an approach follows directly from the whole paradigm of the Pascal language with a pre-description of all types.... But the procedure below is not working yet because it generates unnecessary code...

“not working yet” and “generates unnecessary code” are two different points. The code will behave the same no matter if the compiler generates “unnecessary” code or not.

Code: Pascal  [Select][+][-]
  1.  procedure test41(a: ptrUint); inline;
  2.   begin
  3.     if TypeInfo(a) = TypeInfo(uint8) then Writeln('1')
  4.     else if TypeInfo(a) = TypeInfo(uint16) then Writeln('2')
  5.     else if TypeInfo(a) = TypeInfo(uint32) then Writeln('4')
  6.     else if TypeInfo(a) = TypeInfo(uint64) then Writeln('8');
  7.   end;

I don't know whether this is what you desire, but this will only ever differ if you compile for different platforms. But in that case you could just as well use a type instead of the variable for TypeInfo and thus profit from the already available optimization.
       
ps: there is a hope in the near future to wait at least beta of new functionality, what with pleasure I will test in the real and working project?  I don't think it will be very difficult because, at first glance, I just saw how TypeInfo is implemented in the source code of FPC, just there artificially limited functionality, as opposed to many other built-in compiler functions, which also return a constant.

The important part is to make sure that there really wouldn't be a corner case.

pps: and on a side note, is there any way for "{$mode objfpc}" to use the simpler and more obvious "generics" syntax from the Delphi compatibility relim, just that I don't use?

No, there is not. In the future there will likely be a modeswitch for that, but not until mode ObjFPC supports some features that it currently lacks that mode Delphi supports. Please note however that with the Delphi syntax currently you'll be less able to write complex expressions using inline specializations.

beria

  • Jr. Member
  • **
  • Posts: 70
Re: TypeInfo() - what is it?
« Reply #6 on: December 01, 2022, 11:09:58 pm »
I would say that the safety of such an approach follows directly from the whole paradigm of the Pascal language with a pre-description of all types.... But the procedure below is not working yet because it generates unnecessary code...

“not working yet” and “generates unnecessary code” are two different points. The code will behave the same no matter if the compiler generates “unnecessary” code or not.
[/quote]

The problem is purely in the ultimate optimization. This is the code of an extremely complex and multiplatform telemetry data decoder, I optimized it to the limit, including assembler inserts where necessary, because it is called many millions of times in real time.  So even a set of redundant and unoptimized constant comparison operations is, by measurements, up to minus 5% of performance. At the moment I have different procedures for all dimensions of integer arguments, for this reason, which is very confusing and complicated, but if everything will work as it should, they can be removed in one and universal, which code will be dynamically generated, depending on the type of application.  Exactly the same thing I already did for the variant when the argument of the procedure is a constant. I used to have separate procedures for them too, but beautifully and stably working IsConstValue() helped me a lot.  By the way, it would be very good to rewrite the Move procedure using it.  So, I'm just for a large number of such procedures for the compiler itself, not for the language.
Right now I have something like:
Code: Pascal  [Select][+][-]
  1.   procedure Decoder(a: uint64); inline;
  2.   procedure Decoder(a: uint32); inline;
  3.   procedure Decoder(a: uint16); inline;
  4.   procedure Decoder(a: uint8); inline;

 ...and having to make a mandatory type change for variables with a sign. It's very annoying...

Something like this...

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11771
  • FPC developer.
Re: TypeInfo() - what is it?
« Reply #7 on: December 01, 2022, 11:22:23 pm »
Do you actually have an example where it matters? Since the code you post will first typecast to ptruint, and only then get the typeinfo of ptruint.

beria

  • Jr. Member
  • **
  • Posts: 70
Re: TypeInfo() - what is it?
« Reply #8 on: December 01, 2022, 11:35:57 pm »
Do you actually have an example where it matters? Since the code you post will first typecast to ptruint, and only then get the typeinfo of ptruint.
That's why I write that now it doesn't work and you have to do everything in other ways... But I want it to work like any other compiler function and to get dynamic branching within a procedure...
« Last Edit: December 01, 2022, 11:56:09 pm by beria »

PascalDragon

  • Hero Member
  • *****
  • Posts: 5678
  • Compiler Developer
Re: TypeInfo() - what is it?
« Reply #9 on: December 02, 2022, 05:39:19 pm »
I would say that the safety of such an approach follows directly from the whole paradigm of the Pascal language with a pre-description of all types.... But the procedure below is not working yet because it generates unnecessary code...

“not working yet” and “generates unnecessary code” are two different points. The code will behave the same no matter if the compiler generates “unnecessary” code or not.

The problem is purely in the ultimate optimization. This is the code of an extremely complex and multiplatform telemetry data decoder, I optimized it to the limit, including assembler inserts where necessary, because it is called many millions of times in real time.  So even a set of redundant and unoptimized constant comparison operations is, by measurements, up to minus 5% of performance. At the moment I have different procedures for all dimensions of integer arguments, for this reason, which is very confusing and complicated, but if everything will work as it should, they can be removed in one and universal, which code will be dynamically generated, depending on the type of application.  Exactly the same thing I already did for the variant when the argument of the procedure is a constant. I used to have separate procedures for them too, but beautifully and stably working IsConstValue() helped me a lot.  By the way, it would be very good to rewrite the Move procedure using it.  So, I'm just for a large number of such procedures for the compiler itself, not for the language.
Right now I have something like:
Code: Pascal  [Select][+][-]
  1.   procedure Decoder(a: uint64); inline;
  2.   procedure Decoder(a: uint32); inline;
  3.   procedure Decoder(a: uint16); inline;
  4.   procedure Decoder(a: uint8); inline;

 ...and having to make a mandatory type change for variables with a sign. It's very annoying...

Something like this...

I still don't get what you're trying to achieve. If you retrieve the type information from a PtrUInt parameter then it will always be PtrUInt. It won't change depending on what you pass in. For that you'd need to use generics which wouldn't be a runtime difference either.

And I also don't get your remark regarding Move.

Thaddy

  • Hero Member
  • *****
  • Posts: 15687
  • Censorship about opinions does not belong here.
Re: TypeInfo() - what is it?
« Reply #10 on: January 14, 2023, 06:18:06 pm »
Or maybe I'm missing something in the implementation of this function, and it's still not an internal compiler function, as it's written in the description, or FPC somehow allows you to change the variable's type, which is totally insane to me?
Use GetTypeKind:
Code: Pascal  [Select][+][-]
  1. procedure test40(a: PtrUint); inline;
  2. begin
  3.   if GetTypeKind(a) = GetTypeKind(Byte) then
  4.     Writeln('BYTE');
  5. end;

No, this would only work for your expected result on x86_64 and not on x86_32 a.k.a i386.
Code: Pascal  [Select][+][-]
  1. var a:ptruint;
  2. begin
  3.  writeln(GetTypeKind(a) = GetTypeKind(Byte));  // returns true on 32 bit and false on 64 bit
  4. end.

On 32 bit GetTypeKind(a) returns tkInteger as does GetTypeKind(byte)
On 64 bit GetTypeKind(a) returns tkQword.............

Btw: tkInteger is correct for 32 bit. It does not have to explain signed or not.


« Last Edit: January 14, 2023, 06:32:37 pm by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

bataff

  • Newbie
  • Posts: 4
Re: TypeInfo() - what is it?
« Reply #11 on: December 13, 2023, 07:35:59 pm »
Hello,

Although the conversation has been dry for many months, I am publishing this research here because it will avoid multiplying new topics and because the title of this topic, which interests me, fits very well: TypeInfo() - what is it?

After a few tests, it seems that TypeInfo() doesn't just return the address of the struct of variable's type. It also updates part of the memory in RTTI at compile time (no doubt for practical reasons).
To demonstrate this, here's a little program with two basic records and a procedure that simply displays a series of bytes around an address to scan memory:

(Compiler : fpc version 3.2.2 - System 32 bits - OS : Linux/Ubuntu)
Quote
program Test4;
uses typinfo,sysutils;

type aR = Record a,b : byte; end;
     bR = Record b : byte; end;
   
var a : aR; b : bR; p : pointer;

procedure bytes(p : pointer; p0 : integer; nb : word); //Show nb bytes from p[p0] and write them in the console.
var i : word;
begin
  for i:=0 to nb-1 do begin
    if i mod 16 = 0 then write(IntToHex(sizeUInt(p+p0+i),8),' : ');
    write(IntToHex(pbyte(p+p0+i)^,2),' ');
    if i mod 16 = 15 then writeln else if i mod 4 = 3 then write('|');
    if i mod 128 = 127 then writeln;
  end;
  writeln;
end;

BEGIN
  p:=nil; with a do begin a:=0; b:=0; end; with b do b:=0; //Initialization of the variables (in case - maybe not really necessary here)
  writeln('type(b)[-128] : '); bytes(PTypeInfo(TypeInfo(b)),-128,256); //Show 128 bytes before and from the begining of TTypeInfo struct bR.
//  p:=PTypeInfo(TypeInfo(a));
END.

The penultimate line is currently inactive. If we run this program, this is what we get:

Quote
type(b)[-128] :
080B1050 : 28 1C 0C 08 |9C 40 0C 08 |24 48 0C 08 |00 00 00 00
080B1060 : 00 00 00 00 |00 00 00 00 |00 00 00 00 |00 00 00 00
080B1070 : 00 00 00 00 |00 00 00 00 |00 00 00 00 |00 00 00 00
080B1080 : 00 00 80 00 |00 00 00 00 |00 00 00 00 |00 00 00 00
080B1090 : 00 00 00 00 |00 00 00 00 |00 00 00 00 |00 00 00 00
080B10A0 : 00 00 00 00 |00 00 00 00 |00 00 00 00 |00 00 00 00
080B10B0 : 00 00 00 00 |0D 02 62 52 |00 00 00 00 |01 00 00 00
080B10C0 : 00 00 00 00 |00 00 00 00 |00 00 00 00 |B4 10 0B 08

080B10D0 : 0D 02 62 52 |B4 10 0B 08 |01 00 00 00 |01 00 00 00
080B10E0 : 2C 29 0B 08 |00 00 00 00 |D0 10 0B 08 |0E A4 04 08
080B10F0 : 3E A4 04 08 |26 A4 04 08 |26 A4 04 08 |3E A4 04 08
080B1100 : 3E A4 04 08 |3E A4 04 08 |14 A4 04 08 |3E A4 04 08
080B1110 : 26 A4 04 08 |1A A4 04 08 |32 A4 04 08 |1A A4 04 08
080B1120 : 3E A4 04 08 |38 A4 04 08 |32 A4 04 08 |3E A4 04 08
080B1130 : 3E A4 04 08 |38 A4 04 08 |38 A4 04 08 |20 A4 04 08
080B1140 : 08 A4 04 08 |08 A4 04 08 |3E A4 04 08 |3E A4 04 08 


The TType Info structure of record b starts at the beginning of the second block (080B10D0 : 0D 02 62 52). There's nothing notable in the 1st block, apart from a few bytes at the end corresponding to the BaseTypeInit part of record bR (from 080B10B4 : 0D 02 62 52).

But now, if we activate the penultimate line (deleting the '//') and run the program again, we get this:

Quote
type(b)[-128] :
080A4080 : 00 00 00 00 |00 00 00 00 |00 00 00 00 |00 00 00 00
080A4090 : 00 00 00 00 |00 00 00 00 |00 00 00 00 |00 00 00 00
080A40A0 : 00 00 00 00 |0D 02 61 52 |00 00 00 00 |02 00 00 00
080A40B0 : 00 00 00 00 |00 00 00 00 |00 00 00 00 |A4 40 0A 08
080A40C0 : 0D 02 61 52 |A4 40 0A 08 |02 00 00 00 |02 00 00 00
080A40D0 : 5C 59 0A 08 |00 00 00 00 |5C 59 0A 08 |01 00 00 00
080A40E0 : C0 40 0A 08 |0D 02 62 52 |00 00 00 00 |01 00 00 00
080A40F0 : 00 00 00 00 |00 00 00 00 |00 00 00 00 |E4 40 0A 08

080A4100 : 0D 02 62 52 |E4 40 0A 08 |01 00 00 00 |01 00 00 00
080A4110 : 5C 59 0A 08 |00 00 00 00 |00 41 0A 08 |FE A3 04 08
080A4120 : 2E A4 04 08 |16 A4 04 08 |16 A4 04 08 |2E A4 04 08
080A4130 : 2E A4 04 08 |2E A4 04 08 |04 A4 04 08 |2E A4 04 08
080A4140 : 16 A4 04 08 |0A A4 04 08 |22 A4 04 08 |0A A4 04 08
080A4150 : 2E A4 04 08 |28 A4 04 08 |22 A4 04 08 |2E A4 04 08
080A4160 : 2E A4 04 08 |28 A4 04 08 |28 A4 04 08 |10 A4 04 08
080A4170 : F8 A3 04 08 |F8 A3 04 08 |2E A4 04 08 |2E A4 04 08

Note that the 1st block has largely changed. It contains the type of the previous record aR (from 080A40C0 : 0D 02 61 52). At 080A40CC, there's 02 00 00 00, corresponding to the number of elements (2) in aR, followed by the pairs (PPointer of variable type and offset of variable content from @a) 5C 59 0A 08 |00 00 00 00 and 5C 59 0A 08 |01 00 00 00 for theses two elements in aR. This was not the case when the penultimate line was inactive.
What's more, as this line comes after the instructions, it means that this change by TypeInfo(a) could not have taken place in run time.
Obviously, this remains to be confirmed. I'm not an expert, I'm just discovering this. In other tests, when the penultimate line is inactive, we see the previous record, but only with some of the elements of the previous record - only variables containing a structure).
In any case, if this is indeed the case, it means that an indirect reading of the RTTI (or even worse, an indirect writing), as I've just done, is flawed. The direct use of the TypeInfo() (or more securely PTypeInfo(TypeInfo()) ) function is therefore - at the very least - recommended for each type structure.

Finally, something else after further tests: if you put all the records in a record and do a TypeInfo() only on it, the sub-records will also be 'updated' in memory recursively. (also to be confirmed)

(Edit : Changed 'code' to 'quote' in the two last windows to suppress the unexpected colors -it's better like that-)
« Last Edit: December 17, 2023, 07:01:28 pm by bataff »

PascalDragon

  • Hero Member
  • *****
  • Posts: 5678
  • Compiler Developer
Re: TypeInfo() - what is it?
« Reply #12 on: December 14, 2023, 09:38:41 pm »
In any case, if this is indeed the case, it means that an indirect reading of the RTTI (or even worse, an indirect writing), as I've just done, is flawed. The direct use of the TypeInfo() (or more securely PTypeInfo(TypeInfo()) ) function is therefore - at the very least - recommended for each type structure.

You're wrong. Without the TypeInfo(a) line the compiler will simply not include the type information for the aR type in the final excutable, thus you see the difference.

Also writing to the type information is an absolute no-go. That it currently works on Linux is an implementation detail, because due to issues with relocations the RTTI is not located in a readonly section of the binary. On Windows any write to the RTTI data will raise an exception, because there the data is indeed in a readonly section of the binary and the goal is that for all platforms that support memory protection this will be the case as well.

Finally, something else after further tests: if you put all the records in a record and do a TypeInfo() only on it, the sub-records will also be 'updated' in memory recursively. (also to be confirmed)

Because then the type information for all the involved types is required. This also has nothing to do with “updating”. It's simply the compiler deciding which information to generate. You can also see that when compiling with with -al which will keep the assembly files around.

bataff

  • Newbie
  • Posts: 4
Re: TypeInfo() - what is it?
« Reply #13 on: December 14, 2023, 10:41:23 pm »
PascalDragon :  Thank you very much for your opinion and information, which clearly answers my uncertainties.
 (nothing more to say, good evening or good day :) )

 

TinyPortal © 2005-2018