Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2025-10

Optimization breaks check for overridden function / Is there a better way to check?

12 messages · 7 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

David Jenkins

What is the best way to check if a virtual function in a base class has been overridden by a class that inherits the base class?

In LCL TPrinter.pas the following check is used:

procedure TPrinter.NewPage;
begin
   Inc(fPageNumber);
   if TMethod(@Self.DoNewPage).Code = Pointer(@TPrinter.DoNewPage) then
     begin
     ..
     end
  ..
end

We have run across an optimization problem with the TCocoaPrinter class and that TPrinter code.

TCocoaPrinter = class(TPrinter)

with no override of DeNewPage

Sometime with Level2 or Level3 optimization,
TMethod(@Self.DoNewPage).Code is changed to point to systemh.inc EmptyMethod instead of TPrinter.DoNewPage and the check no longer works appropriately.

Is there another way to make that check that is not affected by optimization?

I will eventually throw a fix to the LCL folks but we use a similar formulation in our code and are concerned that it might also be affected by optimization at some point. Right now it is not because none of the base functions are completely empty, but they are trivial functions that match each other and could be optimized in the future by coalescing them.

Thanks
David Jenkins
Scooter Software.

Reply by email

Pierre Muller
to David Jenkins

Hi David,

this looks very similar to a problem that we are trying to debug for a while, and that I recently submitted as a bug report:

https://gitlab.com/freepascal.org/fpc/source/-/issues/41439

The problem has been nicely simplified by alligator in

https://gitlab.com/freepascal.org/fpc/source/-/issues/41444

There is some problem related to PPU writing...

I hope to be able using this simplified example t understand better this issue, but I am currently still busy with the previous issue, also raised by alligator:

https://gitlab.com/freepascal.org/fpc/source/-/issues/41443

for which I hope to have found a fix.

Pierre

Le 17/10/2025 à 19:35, David Jenkins via fpc-devel a écrit :

Show quoted text 45 lines
What is the best way to check if a virtual function in a base class has been overridden by a class that inherits the base class? In LCL TPrinter.pas the following check is used: procedure TPrinter.NewPage; begin Inc(fPageNumber); if TMethod(@Self.DoNewPage).Code = Pointer(@TPrinter.DoNewPage) then begin .. end .. end We have run across an optimization problem with the TCocoaPrinter class and that TPrinter code. TCocoaPrinter = class(TPrinter) with no override of DeNewPage Sometime with Level2 or Level3 optimization, TMethod(@Self.DoNewPage).Code is changed to point to systemh.inc EmptyMethod instead of TPrinter.DoNewPage and the check no longer works appropriately. Is there another way to make that check that is not affected by optimization? I will eventually throw a fix to the LCL folks but we use a similar formulation in our code and are concerned that it might also be affected by optimization at some point. Right now it is not because none of the base functions are completely empty, but they are trivial functions that match each other and could be optimized in the future by coalescing them. Thanks David Jenkins Scooter Software. _______________________________________________ fpc-devel maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

Reply by email

Sven Barth
to Pierre Muller
Pierre Muller wrote 21 lines
Hi David, this looks very similar to a problem that we are trying to debug for a while, and that I recently submitted as a bug report: https://gitlab.com/freepascal.org/fpc/source/-/issues/41439 The problem has been nicely simplified by alligator in https://gitlab.com/freepascal.org/fpc/source/-/issues/41444 There is some problem related to PPU writing...   I hope to be able using this simplified example t understand better this issue, but I am currently still busy with the previous issue, also raised by alligator: https://gitlab.com/freepascal.org/fpc/source/-/issues/41443 for which I hope to have found a fix.

David's problem is *not* due to a corruction or a bug, but due to an optimization that FPC performs that leads to different behavior, namely changing virtual methods that are empty to EmptyMethod to reduce the number of duplicate (empty) methods in the binary.

Regards,
Sven

Reply by email

Bart
to Sven Barth
David's problem is *not* due to a corruction or a bug, but due to an optimization that FPC performs that leads to different behavior, namely changing virtual methods that are empty to EmptyMethod to reduce the number of duplicate (empty) methods in the binary.

Does that mena that such a check is in essence invalid? IIRC then we have such a check in TCustomEdit in LCL....

Bart

Reply by email

Sven Barth
to Bart
Bart wrote 9 lines
On Fri, Oct 17, 2025 at 9:33 PM Sven Barth via fpc-devel wrote: > David's problem is *not* due to a corruction or a bug, but due to an > optimization that FPC performs that leads to different behavior, namely > changing virtual methods that are empty to EmptyMethod to reduce the > number of duplicate (empty) methods in the binary. Does that mena that such a check is in essence invalid? IIRC then we have such a check in TCustomEdit in LCL....

Well, the language does neither explicitly forbid nor allow it... So you'd have to do a more complete check that includes both the real base method as well as System.EmptyMethod. Maybe best move the general check to a separate function which gets passed both method pointers and then compares those as well as the base one against EmptyMethod. Regards, Sven

Reply by email

Martin Frb
to Sven Barth: David's problem is *not* due to a corruction or a bug, but due to an optimization that…
Sven Barth wrote 27 lines
> What is the best way to check if a virtual function in a base class > has been overridden by a class that inherits the base class? > > In LCL TPrinter.pas the following check is used: > > > procedure TPrinter.NewPage; > begin >   Inc(fPageNumber); >   if TMethod(@Self.DoNewPage).Code = Pointer(@TPrinter.DoNewPage) then >     begin >     .. >     end >  .. > end > > We have run across an optimization problem with the TCocoaPrinter > class and that TPrinter code. > > TCocoaPrinter = class(TPrinter) > > with no override of DeNewPage David's problem is *not* due to a corruction or a bug, but due to an optimization that FPC performs that leads to different behavior, namely changing virtual methods that are empty to EmptyMethod to reduce the number of duplicate (empty) methods in the binary.

I am trying to figure out under which circumstances this would happen?   TPrinter = class...
     procedure DoNewPage; virtual;

And the check really only makes sense for virtual methods. Well (in generics that differs, but for non virtual, both classes are known at compiletime).

So if it is a virtual method:

If the compiler optimizes @TPrinter.DoNewPage to EmptyMethod, then it would put that into the class definition?
So that is into the VMT?
And there is only one VMT?
And self is a pointer (to pointer) to that one VMT?

So then how can one be replaced, but the other not?

He says "with no override of DeNewPage". So he expects them both to be the same.
I could see the opposite fail. If there was on overwritten method, and that was also empty, then the different overwritten empty method would (falsely) report as the same...
But then reporting as different?

Or does the compiler only update the VMT, but does report @TPrinter.DoNewPage to the original code?
If that was the case, would/should that not be considered a bug. If the compiler replaced it, then it should report it for the original too.

If @TPrinter.DoNewPage still reports the original code (and NOT emptyproc) then that forces the original code to be included too (even though the whole optimization was to NOT include it). Because you could then do:
  MyMeth := @TPrinter.DoNewPage;
  MyMeth();
and call the original code.

Reply by email

David Jenkins
to Martin Frb
Martin Frb wrote 71 lines
On 17/10/2025 21:15, Sven Barth via fpc-devel wrote: >> What is the best way to check if a virtual function in a base class >> has been overridden by a class that inherits the base class? >> >> In LCL TPrinter.pas the following check is used: >> >> >> procedure TPrinter.NewPage; >> begin >>   Inc(fPageNumber); >>   if TMethod(@Self.DoNewPage).Code = Pointer(@TPrinter.DoNewPage) then >>     begin >>     .. >>     end >>  .. >> end >> >> We have run across an optimization problem with the TCocoaPrinter >> class and that TPrinter code. >> >> TCocoaPrinter = class(TPrinter) >> >> with no override of DeNewPage > > David's problem is *not* due to a corruction or a bug, but due to an > optimization that FPC performs that leads to different behavior, > namely changing virtual methods that are empty to EmptyMethod to > reduce the number of duplicate (empty) methods in the binary. I am trying to figure out under which circumstances this would happen?   TPrinter = class...      procedure DoNewPage; virtual; And the check really only makes sense for virtual methods. Well (in generics that differs, but for non virtual, both classes are known at compiletime). So if it is a virtual method: If the compiler optimizes @TPrinter.DoNewPage to EmptyMethod, then it would put that into the class definition? So that is into the VMT? And there is only one VMT? And self is a pointer (to pointer) to that one VMT? So then how can one be replaced, but the other not? He says "with no override of DeNewPage". So he expects them both to be the same. I could see the opposite fail. If there was on overwritten method, and that was also empty, then the different overwritten empty method would (falsely) report as the same... But then reporting as different? Or does the compiler only update the VMT, but does report @TPrinter.DoNewPage to the original code? If that was the case, would/should that not be considered a bug. If the compiler replaced it, then it should report it for the original too. If @TPrinter.DoNewPage still reports the original code (and NOT emptyproc) then that forces the original code to be included too (even though the whole optimization was to NOT include it). Because you could then do:   MyMeth := @TPrinter.DoNewPage;   MyMeth(); and call the original code. _______________________________________________ fpc-devel maillist  -  https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

To me it looks like TPrinter.DoNewPage is not updated to System.EmptyMethod as part of the optimization. Which is why the check fails.

My local fix for now is to use

if (TMethod(@Self.DoNewPage).Code = Pointer(@TPrinter.DoNewPage)) or (TMethod(@Self.DoNewPage).Code = System.EmptyMethod) then begin
....
end;

David

Reply by email

Sven Barth
to Martin Frb: I am trying to figure out under which circumstances this would happen?   TPrinter =…

Martin Frb via fpc-devel schrieb am Mo.,
20. Okt. 2025, 22:29:

Show quoted text 47 lines
On 17/10/2025 21:15, Sven Barth via fpc-devel wrote: >> What is the best way to check if a virtual function in a base class >> has been overridden by a class that inherits the base class? >> >> In LCL TPrinter.pas the following check is used: >> >> >> procedure TPrinter.NewPage; >> begin >> Inc(fPageNumber); >> if TMethod(@Self.DoNewPage).Code = Pointer(@TPrinter.DoNewPage) then >> begin >> .. >> end >> .. >> end >> >> We have run across an optimization problem with the TCocoaPrinter >> class and that TPrinter code. >> >> TCocoaPrinter = class(TPrinter) >> >> with no override of DeNewPage > > David's problem is *not* due to a corruction or a bug, but due to an > optimization that FPC performs that leads to different behavior, > namely changing virtual methods that are empty to EmptyMethod to > reduce the number of duplicate (empty) methods in the binary. I am trying to figure out under which circumstances this would happen? TPrinter = class... procedure DoNewPage; virtual; And the check really only makes sense for virtual methods. Well (in generics that differs, but for non virtual, both classes are known at compiletime). So if it is a virtual method: If the compiler optimizes @TPrinter.DoNewPage to EmptyMethod, then it would put that into the class definition? So that is into the VMT? And there is only one VMT? And self is a pointer (to pointer) to that one VMT? So then how can one be replaced, but the other not?

If one is compiled with -O3, but the other is compiled with -O2. The VMT is created for each type, so even if a method isn't overridden it can be replaced in one VMT, but not in the other.

In this case I suspect the LCL and thus TPrinter is compiled with -O2 and the custom code with -O3.

Regards,
Sven

Reply by email

Sven Barth
to Sven Barth: Well, the language does neither explicitly forbid nor allow it... So you'd have to do a…

Sven Barth schrieb am Mo., 20. Okt. 2025,
22:14:

Show quoted text 19 lines
Am 18.10.2025 um 22:39 schrieb Bart via fpc-devel: On Fri, Oct 17, 2025 at 9:33 PM Sven Barth via fpc-devel wrote: David's problem is *not* due to a corruction or a bug, but due to an optimization that FPC performs that leads to different behavior, namely changing virtual methods that are empty to EmptyMethod to reduce the number of duplicate (empty) methods in the binary. Does that mena that such a check is in essence invalid? IIRC then we have such a check in TCustomEdit in LCL.... Well, the language does neither explicitly forbid nor allow it... So you'd have to do a more complete check that includes both the real base method as well as System.EmptyMethod. Maybe best move the general check to a separate function which gets passed both method pointers and then compares those as well as the base one against EmptyMethod.

Sleeping about this a bit I think the solution needs to if either of two methods is redirected to EmptyMethod then the code must assume that the method was overridden, cause that's the only save assumption it can do (and afterall code should handle that gracefully even if it might be less optimal from the developer's point of view).

Regards,
Sven

Reply by email

Martin Frb
to Sven Barth: Martin Frb via fpc-devel schrieb am Mo., 20. Okt. 2025, 22:29: If one is compiled with…
Sven Barth wrote 5 lines
If one is compiled with -O3, but the other is compiled with -O2. The VMT is created for each type, so even if a method isn't overridden it can be replaced in one VMT, but not in the other.

I just did a test, one single program, no units
Compiled with -O3 -al

The VMT contains FPC_EMPTYMETHOD
But the code (asm) still contains P$PROJECT1$_$TFOO_$__$$_BAR

If I enable the "inherited Bar" then it calls the "BAR" method, NOT the FPC_EMPTYMETHOD

If I do
  x := @TFoo.Bar;
I get
   leaq    P$PROJECT1$_$TFOO_$__$$_BAR(%rip),%rax

So the optimizer seems broken to me. What is the point of replacing it in the VMT, if the code is kept non-the less?
It only makes sense if the method is NOT called at all, because then the linker can remove the unused code (which would otherwise be referred too by the VMT).

Sort of what WPO would do.... Maybe even needed for WPO, as otherwise WPO can never know that this method is never called. But, then "x := @TFoo.Bar;" blocks it from being removed, but "x := foo.Bar;" allows its removal.

And what argument could there be that the first must return the correct pointer? If the 2nd can replace it, then the optimizer is based on "the code must be able to work with the replaced pointer", and in that case the same should be allowed to say for the first?

-------------------------------------

Btw, the "code must work with wrong pointer" may be questionable.... Albeit not sure. (maybe a directive to prevent).

Most often I have seen this used to avoid calls to the empty base class. In that case, if it returns true for any empty inherited method => well that is fine too. It just avoids the call to an empty method.

Another use case, I recently found, is in generics.

if _X_.foo  @TKnowGenParam.foo then begin
    // preparation
   _X_variable.foo;
end;

But here again, if TKnowGenParam is empty, then the above skips the preparation (and even can trigger dead code removal). This of course currently works, because _X_ is the gen param, and that is the CLASS.
And it would work too if "foo" is empty, and the @CLASS.meth was optimised too.

Of course if other classes also had an empty foo, yet would want the preparation to run.... But then; why compare the method instead of the class.
In any case that I personally used this, I compared the method, because I want to compare it to the known empty method. (and catch inherited classes too)

program project1;{$Mode objfpc}
type
   TFoo = class
   public
     procedure Bar; virtual;
   end;
   TFoo1 = class(TFoo)
     procedure Bar; override;
   end;
procedure TFoo.Bar;
begin
   //
end;
procedure TFoo1.Bar;
begin
   //inherited Bar;  // calls
end;
var
   f: Tfoo;
   f1: Tfoo1;
   x: Pointer;
begin
   f := TFoo.Create;
   f1 := TFoo1.Create;
   //x := @TFoo.Bar;
   //x := f.Bar;
   f.Bar;
   f1.Bar;
end.

Reply by email

Zoë Peterson
to Sven Barth: Well, the language does neither explicitly forbid nor allow it... So you'd have to do a…
Sven Barth wrote
Am 18.10.2025 um 22:39 schrieb Bart via fpc-devel: > Does that mena that such a check is in essence invalid? > IIRC then we have such a check in TCustomEdit in LCL.... Well, the language does neither explicitly forbid nor allow it...

It's presumably not affected since neither method is empty, but TStream.Seek(Offset: Longint; Origin: Word) is relying on this exact same sort of check.

Zoë Peterson Scooter Software

Reply by email

Marc Weustink
to Sven Barth: Sven Barth schrieb am Mo., 20. Okt. 2025, 22:14: Sleeping about this a bit I think the…
Sven Barth wrote 27 lines
Sven Barth < > schrieb am Mo., 20. Okt. 2025, 22:14: __ Am 18.10.2025 um 22:39 schrieb Bart via fpc-devel: > On Fri, Oct 17, 2025 at 9:33 PM Sven Barth via fpc-devel > wrote: > >> David's problem is *not* due to a corruction or a bug, but due to an >> optimization that FPC performs that leads to different behavior, namely >> changing virtual methods that are empty to EmptyMethod to reduce the >> number of duplicate (empty) methods in the binary. > Does that mena that such a check is in essence invalid? > IIRC then we have such a check in TCustomEdit in LCL.... Well, the language does neither explicitly forbid nor allow it... So you'd have to do a more complete check that includes both the real base method as well as System.EmptyMethod. Maybe best move the general check to a separate function which gets passed both method pointers and then compares those as well as the base one against EmptyMethod. Sleeping about this a bit I think the solution needs to if either of two methods is redirected to EmptyMethod then the code must assume that the method was overridden, cause that's the only save assumption it can do (and afterall code should handle that gracefully even if it might be less optimal from the developer's point of view).

In the LCL case the original method is not empty so it shouldn't be a problem there.

Marc

Reply by email