Re: [fpc-pascal] Traits Proposal
> On Feb 17, 2021, at 9:59 AM, Adriaan van Os via fpc-pascal > wrote: > > 1. multiple inheritance is nice to have, but it has the big issue that the > inheritance tree becomes an inheritance graph and that makes overrules > ambiguent. > 2. interfaces don't have this issue with multiple inheritance, because they > just declare, not implement > 3. but that is also the weakness of interfaces, as we don't want to > reimplement the same code each time > 4. so, we really want an multiple-inheritance graph at the declaration level > with clear tree-like unambigous inheritance paths at the implementation level > 5. thus, the idea is to "push-in" implementation code into an interface that > integrates fully at the declaration level but is independent at the > implementation level. I would say that's right. The problem is that, we like OOP inheritance but when we extend classes we are forced into a single hierarchy. We could use existing delegation patterns and dot-notation like obj.helper.DoSomething or by breaking out entirely and using plain functions but then we lose some of what makes OOP nice, which is, simply saying "something.DoFunction". It may be trivial in terms of typing but it's elegant and in my opinion clean code which is not tedious to write makes happy and more productive. Here's a more practical example but there are other possibilities for composition patterns (I'l think of examples later). I copied this from the RTL and made some changes. Assume you have this hierarchy and you want to add some methods/data to JPEG and GIF images but not TIFF and PNG (they have some special compression needs or something like that). What we would do now is probably just dump them into TCustomBitmap (and bloat TIFF/PNG) or make another subclass just to store the extra methods (which is useless besides being a method store for 2 specific other classes). What I want with traits is that I can extend those 2 classes with specific functionality, still retain the object-orientedness of the syntax and not get trapped trying to inject stuff into a hierarchy where it doesn't really belong anyways. TTIFFBitmap TPNGBitmap TJPEGBitmap TGIFBitmap | TCustomBitmap | TRasterImage | TGraphic | TPersistent | TObject Regards, Ryan Joseph ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] interfaces and smartpointers [was Traits Proposal]
> On Feb 17, 2021, at 4:51 PM, Benito van der Zander via fpc-pascal > wrote: > > I benchmarked it years ago, I do not remember the details. > > But InitInterfacePointers was bad. Just look at it: > That's not great for sure. Fillchar could be a big culprit also actually. I for one would like I way to disable that for special circumstances. I don't Lazarus usually and I can't get the debugger to work on my Mac so tried Compiler Explorer. Not seeing the method calls here though. https://godbolt.org/z/cjY3Te Regards, Ryan Joseph ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] interfaces and smartpointers [was Traits Proposal]
On 17.02.21 21:43, Ryan Joseph via fpc-pascal wrote: So where is your 10% performance hit coming from then? on init: InitInterfacePointers or TInterfacedObject.AfterConstruction? I benchmarked it years ago, I do not remember the details. But InitInterfacePointers was bad. Just look at it: procedure InitInterfacePointers(objclass: tclass;instance : pointer); var ovmt: PVmt; i: longint; intftable: pinterfacetable; Res: pinterfaceentry; begin ovmt := PVmt(objclass); while assigned(ovmt) and {$ifdef VER3_0}(ovmt^.vIntfTable <> @emptyintf){$else}assigned(ovmt^.vIntfTable){$endif} do begin intftable:=ovmt^.vIntfTable; {$ifdef VER3_0} if assigned(intftable) then {$endif VER3_0} begin i:=intftable^.EntryCount; Res:=@intftable^.Entries[0]; while i>0 do begin if Res^.IType = etStandard then ppointer(@(pbyte(instance)[Res^.IOffset]))^:= pointer(Res^.VTable); inc(Res); dec(i); end; end; ovmt:=ovmt^.vParent; end; end; But reusing objects avoids calling all the functions from create. (but reusing is also expensive, besides the memory increase, it needs a free list and thread safe handling of the head of the list) otherwise, during the wrapper function? As Sven pointed out the ref counting happens with records operators also. ARC is general may just be slow if you implement it everywhere. The wrapper function was not included in the 10% ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] interfaces and smartpointers [was Traits Proposal]
> On Feb 17, 2021, at 11:11 AM, Benito van der Zander via fpc-pascal > wrote: > > just open the disassembler window in Lazarus and single step through all the > instructions. Then you see everything > > create alone calls a bunch of methods: > > TInterfacedObject.NewInstance > TObject.NewInstance > getmem > TObject.InitInstance > fillchar > InitInterfacePointers > fpc_pushexceptaddr (on linux only?) > fpc_setjmp > TInterfacedObject.AfterConstruction > So where is your 10% performance hit coming from then? on init: InitInterfacePointers or TInterfacedObject.AfterConstruction? otherwise, during the wrapper function? As Sven pointed out the ref counting happens with records operators also. ARC is general may just be slow if you implement it everywhere. Regards, Ryan Joseph ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] interfaces and smartpointers [was Traits Proposal]
Hi, 1) Implicit cast to ITest which does a runtime lookup (but there's interface name so no string comparison like Supports?) just open the disassembler window in Lazarus and single step through all the instructions. Then you see everything create alone calls a bunch of methods: TInterfacedObject.NewInstance TObject.NewInstance getmem TObject.InitInstance fillchar InitInterfacePointers fpc_pushexceptaddr (on linux only?) fpc_setjmp TInterfacedObject.AfterConstruction (interfaces with TInterfacedObject are even worse than implementing the interface yourself ) The implicit casting is very fast, a single assembly instruction: add $0x20,%rsi But then it also calls fpc_intf_assign for reference counting 2) calling "println" there is a call to a wrapper function (called "thunks")? Yes. Basically the wrapper function casts the interface back to the class (sub $0x20,%rdi) before calling the actual method. Because the class method assumes self is the class and not an interface Bye, Benito On 17.02.21 17:37, Ryan Joseph via fpc-pascal wrote: On Feb 17, 2021, at 8:27 AM, Benito van der Zander via fpc-pascal wrote: var c: ITest; begin c := TTest.Create(123); c.println; So this is where you're getting your performance penalties? Correct me if I'm wrong but two things happen here: 1) Implicit cast to ITest which does a runtime lookup (but there's interface name so no string comparison like Supports?) 2) calling "println" there is a call to a wrapper function (called "thunks")? Regards, Ryan Joseph ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] self corrupted after jump to overridden method
On Wed, 17 Feb 2021 15:27:40 +0100 Mattias Gaertner via fpc-pascal wrote: > Hi, > > I have a strange bug, that suddenly Self becomes a random value when > calling an overridden virtual method. It happens when activating a > TCustomSQLQuery. > Below is a gdb stacktrace with fpc 3.3.1, where Self became 0. On > other runs I saw various other values. My patch turns the crash into a normal Exception: https://bugs.freepascal.org/view.php?id=38503 > Where to start looking? > It happens in Lazarus at designtime, so maybe some vmt issue? Turns out it was an evil combination of optimization and misleading gdb output. Mattias ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] Traits Proposal
To streamline the discussion, let me recapitulate: 1. multiple inheritance is nice to have, but it has the big issue that the inheritance tree becomes an inheritance graph and that makes overrules ambiguent. 2. interfaces don't have this issue with multiple inheritance, because they just declare, not implement 3. but that is also the weakness of interfaces, as we don't want to reimplement the same code each time 4. so, we really want an multiple-inheritance graph at the declaration level with clear tree-like unambigous inheritance paths at the implementation level 5. thus, the idea is to "push-in" implementation code into an interface that integrates fully at the declaration level but is independent at the implementation level. So, the idea (and the purpose of the discussion) I think is to implement (5) in an elegant way. Regards, Adriaan van Os ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] interfaces and smartpointers [was Traits Proposal]
> On Feb 17, 2021, at 8:27 AM, Benito van der Zander via fpc-pascal > wrote: > > var c: ITest; > begin > c := TTest.Create(123); > c.println; So this is where you're getting your performance penalties? Correct me if I'm wrong but two things happen here: 1) Implicit cast to ITest which does a runtime lookup (but there's interface name so no string comparison like Supports?) 2) calling "println" there is a call to a wrapper function (called "thunks")? Regards, Ryan Joseph ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] interfaces and smartpointers [was Traits Proposal]
Hi, (1) A record is not a pointer. So that would require some implicit referencing in the property (2) If it was managed, it would be an allocation, so I don't understand this. I am talking about replacing interfaces with an record. For example this / type ITest = interface procedure println; end; TTest = class(TInterfacedObject, ITest) v: integer; constructor Create(av: integer); procedure println; end; constructor TTest.Create(av: integer); begin v := av; end; procedure TTest.println; begin writeln(v); end; var c: ITest; begin c := TTest.Create(123); c.println; / would become: / type TTestRec = class rc: Integer; v: integer; constructor Create(av: integer); procedure println; end; RTest = record ptr: TTestRec; procedure println; inline; class operator :=(c: TTestRec): RTest; class operator Initialize(var aRec: RTest); class operator finalize(var aRec: RTest); class operator AddRef(var aRec: RTest); end; constructor TTestRec.Create(av: integer); begin v := av; end; procedure TTestRec.println; begin writeln(v); end; procedure RTest.println; begin ptr.println; //the wrapper function is inlined end; class operator RTest.:=(c: TTestRec): RTest; begin result := default(RTest); result.ptr := c; if c <> nil then InterlockedIncrement(c.rc); end; class operator RTest.Initialize(var aRec: RTest); begin aRec.ptr := nil; end; class operator RTest.finalize(var aRec: RTest); begin if aRec.ptr <> nil then if InterlockedDecrement(aRec.ptr.rc) = 0 then aRec.ptr.Free; end; class operator RTest.AddRef(var aRec: RTest); begin if aRec.ptr <> nil then InterlockedIncrement(aRec.ptr.rc); end; var r: RTest; begin r := TTestRec.Create(123); r.println; / Or even replace the class with a record, too: / type PTestRec2 = ^TTestRec2; TTestRec2 = record rc: Integer; v: integer; class function Create(av: integer): PTestRec2; static; procedure println; end; RTest2 = record ptr: ^TTestRec2; procedure println; inline; class operator :=(c: PTestRec2): RTest2; class operator Initialize(var aRec: RTest2); class operator finalize(var aRec: RTest2); class operator AddRef(var aRec: RTest2); end; class function TTestRec2.Create(av: integer): PTestRec2; begin new(result); result^.rc := 0; result^.v := av; end; procedure TTestRec2.println; begin writeln(v); end; procedure RTest2.println; begin ptr^.println; end; class operator RTest2.:=(c: PTestRec2): RTest2; begin result := default(RTest2); result.ptr := c; if c <> nil then InterlockedIncrement(c^.rc); end; class operator RTest2.Initialize(var aRec: RTest2); begin aRec.ptr := nil; end; class operator RTest2.finalize(var aRec: RTest2); begin if aRec.ptr <> nil then if InterlockedDecrement(aRec.ptr^.rc) = 0 then dispose(aRec.ptr); end; class operator RTest2.AddRef(var aRec: RTest2); begin if aRec.ptr <> nil then InterlockedIncrement(aRec.ptr^.rc); end; var r2: RTest2; begin r2 := TTestRec2.Create(123); r2.println; / Not sure if it is actually faster. That needs to be investigated. But it definitely helps with the memory usage: writeln(ttest.InstanceSize); writeln(ttestrec.InstanceSize); writeln(sizeof(ttestrec2)); 40 16 8 With many small objects it should be faster just because it fits better in the cache. Cheers, Benito On 17.02.21 14:31, Marco van de Voort via fpc-pascal wrote: Op 2021-02-17 om 00:02 schreef Benito van der Zander via fpc-pascal: And there often is a lot of unintentional deep copying. This is also why a property returning a record is fairly useless except for extremely small records like TPoint (and even that is not optimal no But a managed record to replace an interface, would only contain a single pointer/class ref. That can be copied fast (1) A record is not a pointer. So that would require some implicit referencing in the property (2) If it was managed, it would be an allocation, so I don't understand this. ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] Traits Proposal
> On Feb 17, 2021, at 6:10 AM, Sven Barth wrote: > > Simply because no one has come around to implement it yet. The class type > case is more complicated than the interface case. (Same would be true for > record and objects) > So, yes, we'll need to implement this first which would finally bring FPC up > to Delphi compatibility regarding interface delegation. So "class type method resolution" is what's missing? I never used the interface method resolution so I don't really understand this feature. What needs to happen as far as the compiler is concerned? What if you want to implement virtual methods in a base class? This is kind of the core of "composition over inheritance" so it needs to be handled via method resolution I assume. (* * Shape *) type TShape = class protected procedure Draw; virtual; abstract; end; (* * Circle *) type ICircle = interface procedure Draw; end; type TCircle = class(ICircle) procedure Draw; end; (* * My Shape *) type TMyShape = class(TShape, ICircle) private m_circle: TCircle; protected procedure ICircle.Draw = Draw; public property circle: TCircle read m_circle implements ICircle; end; Regards, Ryan Joseph ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] Traits Proposal
> On Feb 17, 2021, at 6:17 AM, Sven Barth wrote: > > No. With wrapper function he means the thunks that are responsible for > adjusting the instance pointer of the interface to the instance pointer of > the class before jumping to the method of the class. Oh, so this is some extra code that runs every time you call a method of an interface? I understand interfaces and use them sometimes, but not all the internal workings in the compiler and how they're implemented. > I have the feeling that you don't really know how interfaces work. > ARC would have the exact same penalty as the reference counting of > interfaces, because that is essentially what the compiler would use: call > some function that increases/decreases the reference count of the instance. > That is *exactly* what is done for interfaces. > > Also the interface is not created. It's part of the class instance. You > simply have a pointer sized value that points to that. In the ideal situation > of *only* working with the interface you can simply forget the class > instance, cause the class will be freed once the reference count reached 0. > Thus there will be *no* additional storage involved. I'm talking about the decoupling of the interface and class and how that's a strange way to implement ARC. These wrapper functions and runtime lookups for casting are another side of it. I would hope someday we can implement ref counting in classes the same way as dynamic arrays and ansistrings, i.e. you just pass the reference and forget about it. Regards, Ryan Joseph ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
[fpc-pascal] self corrupted after jump to overridden method
Hi, I have a strange bug, that suddenly Self becomes a random value when calling an overridden virtual method. It happens when activating a TCustomSQLQuery. Below is a gdb stacktrace with fpc 3.3.1, where Self became 0. On other runs I saw various other values. Where to start looking? It happens in Lazarus at designtime, so maybe some vmt issue? #0 0x013cba40 in INTERNALOPEN (this=0x0) at fcl-db/src/sqldb/sqldb.pp:3140 #1 0x01250f1d in DOINTERNALOPEN (this=0x7fffc5c0) at fcl-db/src/base/dataset.inc:410 #2 0x012520f1 in OPENCURSOR (this=0x752aab50, INFOQUERY=184) at fcl-db/src/base/dataset.inc:958 #3 0x013cac53 in OPENCURSOR (this=0x752aab50, INFOQUERY=false) at fcl-db/src/sqldb/sqldb.pp:2824 #4 0x01252518 in SETACTIVE (this=0x752aab50, VALUE=true) at fcl-db/src/base/dataset.inc:1093 #5 0x0057a662 in SETORDPROP (INSTANCE=0x7fffd001, PROPINFO=0x752aab50, VALUE=1) at ../objpas/typinfo.pp:1898 #6 0x00a7a239 in SETORDVALUE (this=0x7fffc47155e0, NEWVALUE=1) at propedits.pp:3315 Mattias ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] Traits Proposal
Op 2021-02-17 om 00:02 schreef Benito van der Zander via fpc-pascal: And there often is a lot of unintentional deep copying. This is also why a property returning a record is fairly useless except for extremely small records like TPoint (and even that is not optimal no But a managed record to replace an interface, would only contain a single pointer/class ref. That can be copied fast (1) A record is not a pointer. So that would require some implicit referencing in the property (2) If it was managed, it would be an allocation, so I don't understand this. ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] Traits Proposal
Ryan Joseph via fpc-pascal schrieb am Mi., 17. Feb. 2021, 02:21: > > > > On Feb 16, 2021, at 3:35 PM, Benito van der Zander via fpc-pascal < > fpc-pascal@lists.freepascal.org> wrote: > > > > But it is not calling the method, it is calling the wrapper function > > what you say wrapper function do you mean the ref counting functions when > you pass around the interface? > No. With wrapper function he means the thunks that are responsible for adjusting the instance pointer of the interface to the instance pointer of the class before jumping to the method of the class. I guess for ref counting you're supposed to cast the class to the interface > (which is an expensive runtime lookup) and then pass that thing around? > That seems like a crazy way to get ref counting plus you need to cast the > interface back to the class if you want to call other methods? I still > don't get this pattern. ARC should be handled by the compiler and be on the > class itself, not some additional object you need to create and then store. > I have the feeling that you don't really know how interfaces work. ARC would have the exact same penalty as the reference counting of interfaces, because that is essentially what the compiler would use: call some function that increases/decreases the reference count of the instance. That is *exactly* what is done for interfaces. Also the interface is not created. It's part of the class instance. You simply have a pointer sized value that points to that. In the ideal situation of *only* working with the interface you can simply forget the class instance, cause the class will be freed once the reference count reached 0. Thus there will be *no* additional storage involved. Regards, Sven > ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Re: [fpc-pascal] Traits Proposal
Ryan Joseph via fpc-pascal schrieb am Mi., 17. Feb. 2021, 02:15: > > > > On Feb 16, 2021, at 2:43 PM, Sven Barth via fpc-pascal < > fpc-pascal@lists.freepascal.org> wrote: > > > > FPC currently does not yet support class types not to mention records > and objects which is what I had mentioned earlier already. > > Yes I see this on https://freepascal.org/docs-html/ref/refse47.html. Why > isn't this supported? I guess as part of this proposal we need to implement > this feature as well? I'm not familiar with this area of the language so > I'm not sure what this entails. > Simply because no one has come around to implement it yet. The class type case is more complicated than the interface case. (Same would be true for record and objects) So, yes, we'll need to implement this first which would finally bring FPC up to Delphi compatibility regarding interface delegation. > > > >> It's a strange syntax compared to anything else we allow in classes and > the boiler plate is really adding up now. "procedure Draw" is going to be > in 4 lines now! > >> > >> It would be nicer if they made this into one line so we don't have yet > another set of duplicate method signatures, i.e.: > >> > >> procedure Draw as ICircle.Draw; > > > > I can't shake the feeling that all you complain about is writing even a > little bit too much. Pascal is *not* about using as less code as possible, > but to be as correct as possible and the syntax of interface delegation > allows exactly that. > > Being too verbose is clunky in my opinion. That method resolution syntax > is just kind of an odd addition for classes to my eyes. "procedure > Type.Name = Name;" doesn't have any precedence inside classes/records so it > stands out. > Again, the syntax is much more powerful than what you suggested. Also it's the existing syntax and not something new. Not to mention that it only needs to be used if you need to implement a method yourself. Iff(!) we find out down the line once we have the basic functionality working, that people are struggling with this, we can still think about improving something, but for now I'd prefer to add as less new syntaxes as possible (especially if a syntax that does provide this exact functionality *does* exist already, no matter if it's too verbose for some). Regards, Sven > ___ fpc-pascal maillist - fpc-pascal@lists.freepascal.org https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal