Discussion:
[fpc-devel] Defer keyword
Ryan Joseph via fpc-devel
2021-05-06 15:38:59 UTC
Permalink
Something which annoys me about Pascal is cleanup in which a function exits in multiple places but there is no formal way to free memory which may be used in the current scope. I say ultimately Pascal needs some opt-in automatic reference counting for TObject but the "defer" keyword would be helpful alternative to what we have now, which is nothing.

The concept is very easy to understand and should be easy to implement by simply making a "defer" statement node which is added to a list and then called during function finalization like the other ref counted objects (dynamic array, interfaces etc....).

I've seen it appear in multiple languages already and it's a sound idea in my opinion. Is this something worth perusing for Pascal?

https://www.hackingwithswift.com/example-code/language/how-to-delay-execution-of-code-using-the-defer-keyword
https://www.geeksforgeeks.org/defer-keyword-in-golang/

procedure DoStuff;
begin
obj := TObject.Create;
defer objects.Free;

while true do
begin
// don't worry, obj will be freed safely
if not obj.TrySomething then
exit;
end;
end;

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lis
Marco van de Voort via fpc-devel
2021-05-06 16:44:50 UTC
Permalink
Post by Ryan Joseph via fpc-devel
Something which annoys me about Pascal is cleanup in which a function exits in multiple places but there is no formal way to free memory which may be used in the current scope. I say ultimately Pascal needs some opt-in automatic reference counting for TObject but the "defer" keyword would be helpful alternative to what we have now, which is nothing.
The concept is very easy to understand and should be easy to implement by simply making a "defer" statement node which is added to a list and then called during function finalization like the other ref counted objects (dynamic array, interfaces etc....).
But those types have refcounting built-in and always active. Things like
defer don't, which makes that all objects gets refcounting overhead in
case somebody needs it for "defer".

Contrary to Pascal both the language you reference have garbage
collectors, so their objects are already managed anyway,



_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc
Ryan Joseph via fpc-devel
2021-05-06 17:11:33 UTC
Permalink
But those types have refcounting built-in and always active. Things like defer don't, which makes that all objects gets refcounting overhead in case somebody needs it for "defer".
Contrary to Pascal both the language you reference have garbage collectors, so their objects are already managed anyway,
The idea of defer isn't necessarily about memory management but rather literally just deferring a statements execution until a predicable point in execution (end of a block or function). Those articles mentioned using them for file IO also so that you can be sure you're going to close the open file handle even if the function returns before. Memory management is just one obvious use case since we have this problem in Pascal often.

I don't think this even affects ref counting of existing types because it all it does it move the statement to the end of the block. Maybe I'm not understanding your point though.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/
J. Gareth Moreton via fpc-devel
2021-05-06 17:39:11 UTC
Permalink
In the example given:

obj := TObject.Create;
defer objects.Free;

What's wrong with Pascal's existing functionality?

obj := TObject.Create;
try
...
finally
 objects.Free;
end;

If there's a concern about performance penalty, maybe the compiler can
work something out for simple finally blocks and just copy the code to
any Exit nodes found rather than calling the pseudo-procedure that a
try...finally block creates.

Gareth aka. Kit
Post by Ryan Joseph via fpc-devel
But those types have refcounting built-in and always active. Things like defer don't, which makes that all objects gets refcounting overhead in case somebody needs it for "defer".
Contrary to Pascal both the language you reference have garbage collectors, so their objects are already managed anyway,
The idea of defer isn't necessarily about memory management but rather literally just deferring a statements execution until a predicable point in execution (end of a block or function). Those articles mentioned using them for file IO also so that you can be sure you're going to close the open file handle even if the function returns before. Memory management is just one obvious use case since we have this problem in Pascal often.
I don't think this even affects ref counting of existing types because it all it does it move the statement to the end of the block. Maybe I'm not understanding your point though.
Regards,
Ryan Joseph
_______________________________________________
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
--
This email has been checked for viruses by Avast antivirus software.
https://www.avast.com/antivirus

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freep
Ryan Joseph via fpc-devel
2021-05-06 17:53:03 UTC
Permalink
Post by Ryan Joseph via fpc-devel
obj := TObject.Create;
defer objects.Free;
What's wrong with Pascal's existing functionality?
obj := TObject.Create;
try
...
finally
objects.Free;
end;
If there's a concern about performance penalty, maybe the compiler can work something out for simple finally blocks and just copy the code to any Exit nodes found rather than calling the pseudo-procedure that a try...finally block creates.
I didn't know try..finally even worked like that. :) I thought it was just for exceptions but I see it captures exit also. The defer keyword is nicer on the eyes I would say because it don't require wrapping the entire function in a big block of code.

So never mind then I guess. I'll start using try..finally and see how that works for me.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/l
J. Gareth Moreton via fpc-devel
2021-05-06 18:02:53 UTC
Permalink
The rule with try...finally is that, outside of something completely
catastrophic that destroys the program flow, is that once you enter the
try part, the finally part is guaranteed to be executed no matter how
you leave it.

Gareth aka. Kit
Post by Ryan Joseph via fpc-devel
Post by Ryan Joseph via fpc-devel
obj := TObject.Create;
defer objects.Free;
What's wrong with Pascal's existing functionality?
obj := TObject.Create;
try
...
finally
objects.Free;
end;
If there's a concern about performance penalty, maybe the compiler can work something out for simple finally blocks and just copy the code to any Exit nodes found rather than calling the pseudo-procedure that a try...finally block creates.
I didn't know try..finally even worked like that. :) I thought it was just for exceptions but I see it captures exit also. The defer keyword is nicer on the eyes I would say because it don't require wrapping the entire function in a big block of code.
So never mind then I guess. I'll start using try..finally and see how that works for me.
Regards,
Ryan Joseph
_______________________________________________
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
--
This email has been checked for viruses by Avast antivirus software.
https://www.avast.com/antivirus

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-deve
Sven Barth via fpc-devel
2021-05-06 22:05:53 UTC
Permalink
Post by J. Gareth Moreton via fpc-devel
The rule with try...finally is that, outside of something completely
catastrophic that destroys the program flow, is that once you enter the
try part, the finally part is guaranteed to be executed no matter how
you leave it.
There are two exceptions (pun not intended :P ):

- Halt (or any other OS function that never returns and terminates the
process)
- LongJmp (because that knows nothing about exception handlers)

Other than that, you're right and what Ryan is trying to do is definitely
the intended purpose of try ... finally.

Regards,
Sven
Ryan Joseph via fpc-devel
2021-05-06 22:17:52 UTC
Permalink
Other than that, you're right and what Ryan is trying to do is definitely the intended purpose of try ... finally.
Is there any runtime code involved with try..finally or does it just reorganize the code to run at the end of the block? My understanding of the defer keyword is that is was just a fancy way to move some code into a block which always gets run with a function exits.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists
J. Gareth Moreton via fpc-devel
2021-05-06 22:26:25 UTC
Permalink
There is some special handling involved in that the code inside the try
part (I think) is effectively a nested procedure.  It's important in the
context of stack unwinding when an exception occurs.  There is a
performance penalty when using them, which one reason why the compiler
sources don't use them.  There's probably other reasons too.  There
might be some speed-up potential where standard Exit calls are
concerned, but I'm not sure.

Gareth aka. Kit
Post by Ryan Joseph via fpc-devel
Other than that, you're right and what Ryan is trying to do is definitely the intended purpose of try ... finally.
Is there any runtime code involved with try..finally or does it just reorganize the code to run at the end of the block? My understanding of the defer keyword is that is was just a fancy way to move some code into a block which always gets run with a function exits.
Regards,
Ryan Joseph
_______________________________________________
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
--
This email has been checked for viruses by Avast antivirus software.
https://www.avast.com/antivirus

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/f
Ryan Joseph via fpc-devel
2021-05-06 22:32:42 UTC
Permalink
There is a performance penalty when using them, which one reason why the compiler sources don't use them. There's probably other reasons too. There might be some speed-up potential where standard Exit calls are concerned, but I'm not sure.
I just did a search and I did indeed see a few try..finally blocks but not many. As I understand it there is a "finalization" section of each procedure which is used for ref counted objects so I assumed the statement was merely moved to that location but I guess there's some concerns over exceptions.

Either way looking at the programming language landscape the better way forward seems to be some opt-in ARC for TObject but I don't know if the compiler team is receptive to that (Sven made some attempt years ago but abandoned it). it's kind of frustrating that we have ref counted types but that isn't extended to classes. Hopefully that's something we can tackle one of these days...

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/c
Nikolai Zhubr via fpc-devel
2021-05-06 23:36:17 UTC
Permalink
Hi,

07.05.2021 1:32, Ryan Joseph via fpc-devel:
[...]
Post by Ryan Joseph via fpc-devel
it's kind of frustrating that we have ref counted types but that isn't extended to classes.
Indeed. However, unfortunately classes are substantially different in
that they can cause reference circles, which then cause damage to ref
counting, unless some severe complications are implemented, and then it
will probably get close to garbage collection. Well, AFAIU.


Regards,
Nikolai
Post by Ryan Joseph via fpc-devel
Regards,
Ryan Joseph
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinf
Martin Frb via fpc-devel
2021-05-06 23:41:37 UTC
Permalink
Post by Nikolai Zhubr via fpc-devel
Indeed. However, unfortunately classes are substantially different in
that they can cause reference circles,
You can already cause ref circles, no classes needed.

type
  TR = record
    a: array of TR;
  end;

var
  x: TR;
begin
  SetLength(x.a,99);
  x.a[0] := x;
end.


_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-dev
Nikolai Zhubr via fpc-devel
2021-05-07 00:54:40 UTC
Permalink
Hi Martin,
Post by Martin Frb via fpc-devel
Post by Nikolai Zhubr via fpc-devel
Indeed. However, unfortunately classes are substantially different in
that they can cause reference circles,
You can already cause ref circles, no classes needed.
Yes, records and objects are the same as classes in this respect. You
cannot do circles with any other types, AFAIK.


Regards,
Nikolai
Post by Martin Frb via fpc-devel
type
TR = record
a: array of TR;
end;
var
x: TR;
begin
SetLength(x.a,99);
x.a[0] := x;
end.
_______________________________________________
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.free
Ryan Joseph via fpc-devel
2021-05-07 01:14:27 UTC
Permalink
Post by Martin Frb via fpc-devel
You can already cause ref circles, no classes needed.
type
TR = record
a: array of TR;
end;
var
x: TR;
begin
SetLength(x.a,99);
x.a[0] := x;
end.
This can be detected at compile and at least give a warning. "a" is a member of TR and the element type of "a" is TR, then we're assigning TR to said array. It's that simple I think.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/
Ryan Joseph via fpc-devel
2021-05-07 01:31:36 UTC
Permalink
Post by Ryan Joseph via fpc-devel
This can be detected at compile and at least give a warning. "a" is a member of TR and the element type of "a" is TR, then we're assigning TR to said array. It's that simple I think.
It also occurs to me that record management operators already allow these types of circular references. It's just par for the course with ref counting and something programmers need to be aware of.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mai
Sven Barth via fpc-devel
2021-05-07 20:52:11 UTC
Permalink
Post by Ryan Joseph via fpc-devel
Post by Ryan Joseph via fpc-devel
This can be detected at compile and at least give a warning. "a" is a
member of TR and the element type of "a" is TR, then we're assigning TR to
said array. It's that simple I think.
It also occurs to me that record management operators already allow these
types of circular references. It's just par for the course with ref
counting and something programmers need to be aware of.
As said the main problem of reference counting on object instances,
especially if enabled by default like the Delphi NextGen compiler did, will
lead to problems in *existing* code and thus is a no-go.

Regards,
Sven
Ryan Joseph via fpc-devel
2021-05-07 21:16:58 UTC
Permalink
As said the main problem of reference counting on object instances, especially if enabled by default like the Delphi NextGen compiler did, will lead to problems in *existing* code and thus is a no-go.
What did you think about me other email that had ideas to add compiler directives like $M+? The way record management operators are implemented is that if you include any of the operators then the type becomes "managed" in the same way other ref counted types are handled.

For classes this is different because there is a hierarchy which is now altered but the compiler could still insert a hidden super class above it and use that to store the extra data. Indeed this would mean that existing classes (like the RTL) would not be eligible for reference counting unless it was compiled using said directive.

For example the follow class:

{$RETAINED+}
type
TMyObject = class(TBaseClass)
end;
{$RETAINED-}

would become:

type
TMyObject_RefCounted = class abstract(TBaseClass)
strict private
refCount: LongInt;
end;
TMyObject = class(TMyObject_RefCounted)
end;

and now "TMyObject" is a managed type and Initialize/Finalize/AddRef/Copy will be called. It occurs to me now though that the ref counting would be tied to the type so if you cast the class to TObject and passed it around then ref counting wouldn't happen. Not sure if that's a deal breaker or not but it could easily cause hard to fix memory leaks.... just like normal classes. :)

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/
Sven Barth via fpc-devel
2021-05-08 13:59:30 UTC
Permalink
Post by Ryan Joseph via fpc-devel
As said the main problem of reference counting on object instances, especially if enabled by default like the Delphi NextGen compiler did, will lead to problems in *existing* code and thus is a no-go.
What did you think about me other email that had ideas to add compiler directives like $M+? The way record management operators are implemented is that if you include any of the operators then the type becomes "managed" in the same way other ref counted types are handled.
For classes this is different because there is a hierarchy which is now altered but the compiler could still insert a hidden super class above it and use that to store the extra data. Indeed this would mean that existing classes (like the RTL) would not be eligible for reference counting unless it was compiled using said directive.
{$RETAINED+}
type
TMyObject = class(TBaseClass)
end;
{$RETAINED-}
type
TMyObject_RefCounted = class abstract(TBaseClass)
strict private
refCount: LongInt;
end;
TMyObject = class(TMyObject_RefCounted)
end;
and now "TMyObject" is a managed type and Initialize/Finalize/AddRef/Copy will be called. It occurs to me now though that the ref counting would be tied to the type so if you cast the class to TObject and passed it around then ref counting wouldn't happen. Not sure if that's a deal breaker or not but it could easily cause hard to fix memory leaks.... just like normal classes. :)
It has the exact same problems that my branch had (especially the
interaction of reference counted instances with non-reference counted ones).

Using a variable/parameter/field based approach (like the idea with
managed records and default fields) is the more flexible one compared to
the type or instance based one and thus it's more favorable.

Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/l
Ryan Joseph via fpc-devel
2021-05-08 16:23:29 UTC
Permalink
It has the exact same problems that my branch had (especially the interaction of reference counted instances with non-reference counted ones).
Using a variable/parameter/field based approach (like the idea with managed records and default fields) is the more flexible one compared to the type or instance based one and thus it's more favorable.
I still don't understand how the record approach is that much different from a managed class type which calls the same set of management operators. Can we make a pros-cons list to clear this up and give an example of "especially the interaction of reference counted instances with non-reference counted ones"?

Here's the most recent things we brought up:

- Records can't be cast in a way that would break reference counting (like a managed class being cast to TObject would).
- Generic records would create a proliferation of new types for all classes you wanted managed, so instead of using TFPGList<TSomeObject> you're using TManagedSomeObjectList or TManaged<TFPGList<TSomeObject>>, or worse yet "specialize TManaged<specialize TFPGList<TSomeObject>>"

Otherwise the same set of circular references exists but I'm not sure about your concern about mixing managed types yet.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepasca
Sven Barth via fpc-devel
2021-05-08 17:18:09 UTC
Permalink
Post by Ryan Joseph via fpc-devel
It has the exact same problems that my branch had (especially the interaction of reference counted instances with non-reference counted ones).
Using a variable/parameter/field based approach (like the idea with managed records and default fields) is the more flexible one compared to the type or instance based one and thus it's more favorable.
I still don't understand how the record approach is that much different from a managed class type which calls the same set of management operators. Can we make a pros-cons list to clear this up and give an example of "especially the interaction of reference counted instances with non-reference counted ones"?
- Records can't be cast in a way that would break reference counting (like a managed class being cast to TObject would).
- Generic records would create a proliferation of new types for all classes you wanted managed, so instead of using TFPGList<TSomeObject> you're using TManagedSomeObjectList or TManaged<TFPGList<TSomeObject>>, or worse yet "specialize TManaged<specialize TFPGList<TSomeObject>>"
Otherwise the same set of circular references exists but I'm not sure about your concern about mixing managed types yet.
It's not about reference counted classes vs. managed records, but about
whether it's *per type* or *per variable*, the implementation details
are completely irrelevant for now.

And the problems are assigning a reference counted class instance to a
non-reference counted variable or parameter. Also casting such a
reference counted class to a non-reference counted one (e.g. to
TObject). Allowing these kind of operations would either need to be
forbidden which would restrict the usability of such classes or they
could potentially lead to memory leaks or premature freeing. Not to
mention how calling the destructor would react if the reference count
isn't 0.

By using a mechanism based on the variable/field/parameter type you have
a much more fine grained control and if one leaves out the implicit
assignment from the wrapped class type to the non-wrapped one then one
needs to do an explicit conversion.

Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-deve
Ryan Joseph via fpc-devel
2021-05-08 17:38:49 UTC
Permalink
It's not about reference counted classes vs. managed records, but about whether it's *per type* or *per variable*, the implementation details are completely irrelevant for now.
So the biggest concern you see if that classes are easier to assign to non-reference counted classes? The only difference between classes and records in this regard is that records give errors unless you assign directly to the same record type, where classes can be assigned to super-classes which may not be managed.

As you say there would need to be at least a warning if you cast a managed class to another class type or make it forbidden completely. I don't see that as a deal breaker personally but you seem to feel pretty strongly about it.

Anyways I wrote up a little wiki with some potential implementation notes about a default property (which overlaps on the "defaults implements" as traits stuff). Important points are restricting what types can be default properties (classes and maybe/probably typed pointers) and limiting hoisting to subscripting, so it's kind of like the -> operator overload in C++.

https://github.com/genericptr/freepascal/wiki/Default-property

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo
Sven Barth via fpc-devel
2021-05-09 09:40:33 UTC
Permalink
Post by Ryan Joseph via fpc-devel
It's not about reference counted classes vs. managed records, but about whether it's *per type* or *per variable*, the implementation details are completely irrelevant for now.
So the biggest concern you see if that classes are easier to assign to non-reference counted classes? The only difference between classes and records in this regard is that records give errors unless you assign directly to the same record type, where classes can be assigned to super-classes which may not be managed.
As you say there would need to be at least a warning if you cast a managed class to another class type or make it forbidden completely. I don't see that as a deal breaker personally but you seem to feel pretty strongly about it.
It seems that you don't work much with classes then. If one disallows
the assignment of a reference counted class to a non-reference counted
one then you can't use e.g. TStringList.Objects. There is also the
problem of method pointers, which essentially only have a Pointer as
Self data. Also a reference might escape in a parent class (for this
example I'll use the syntax I used in my branch):

=== code begin ===

{$mode objfpc}

type
  TTest = class
  protected
    procedure DoSomething;
  end;

  TTestSub = class refcounted(TTest)
  public
    procedure Test;
  end;

procedure TTest.DoSomething;
begin
  // maybe this functions stores the reference
  SomeFuncThatTakesAObject(Self);
end;

procedure TTest.Test;
begin
  DoSomething;
end;

=== code end ===

Obviously these problems won't be solved with the alternative approach
either, but likely one can make clear more easily that the use case is
for local instances.
Post by Ryan Joseph via fpc-devel
Anyways I wrote up a little wiki with some potential implementation notes about a default property (which overlaps on the "defaults implements" as traits stuff). Important points are restricting what types can be default properties (classes and maybe/probably typed pointers) and limiting hoisting to subscripting, so it's kind of like the -> operator overload in C++.
https://github.com/genericptr/freepascal/wiki/Default-property
It shouldn't hoist only public members, it should hoist according to the
visibility rules (thus the hoisting depends on the callsite), otherwise
it won't behave like Pascal classes do and thus we can forget it right away.

Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-deve
Ryan Joseph via fpc-devel
2021-05-09 14:58:14 UTC
Permalink
I use classes all the time but I thought that any assignments or passing to function args call the management operators. So if you pass a managed class to a TStringList.Add for example then AddRef will indeed by called. You're saying this isn't the case? I know the FGL classes can work with ref counted objects so why is it any different if a class type was managed and then passed into one of these types?
Post by Ryan Joseph via fpc-devel
Anyways I wrote up a little wiki with some potential implementation notes about a default property (which overlaps on the "defaults implements" as traits stuff). Important points are restricting what types can be default properties (classes and maybe/probably typed pointers) and limiting hoisting to subscripting, so it's kind of like the -> operator overload in C++.
https://github.com/genericptr/freepascal/wiki/Default-property
It shouldn't hoist only public members, it should hoist according to the visibility rules (thus the hoisting depends on the callsite), otherwise it won't behave like Pascal classes do and thus we can forget it right away.
So this means if the property is in the private section it looks at private visibility in the parent class? Yeah that's probably right we need to do that.

Some things:

1) What do read/write access even mean in the context of the default properties? The terms don't really make much sense given what the the property does. Right now the property could be read only or write only but those don't really have any affect on the hoisting process itself so it's kind of deceptive. Methods are always "read-only" but i guess you could hoist fields/properties and inherit the access level of the default property. No idea if that's helpful or just adding needless complexity. Any ideas?

2) I also think there needs to be another name for the feature than "default property" since this term is already used for array indexers and could even be used for something like traits in the future (traits would be reusing much of this code). I need to add some enum names and default_property is already used so I need to think of something else.

3) What about allowing type pointers as default properties? This should be possible and is in the spirit of the feature anyways, that is ref counting. We may need to add some additional logic to properties (just internally) so that they can be used with pointers but I'm not sure about that yet.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-deve
Sven Barth via fpc-devel
2021-05-10 21:05:41 UTC
Permalink
Post by Ryan Joseph via fpc-devel
I use classes all the time but I thought that any assignments or passing to function args call the management operators. So if you pass a managed class to a TStringList.Add for example then AddRef will indeed by called. You're saying this isn't the case? I know the FGL classes can work with ref counted objects so why is it any different if a class type was managed and then passed into one of these types?
Why should they? You pass the reference to a non-reference counted
parameter/field/variable, the reference count is increased and then
what? It sits there for the remaining life time of the program, because
nothing decrements the reference count?
Post by Ryan Joseph via fpc-devel
Post by Ryan Joseph via fpc-devel
Anyways I wrote up a little wiki with some potential implementation notes about a default property (which overlaps on the "defaults implements" as traits stuff). Important points are restricting what types can be default properties (classes and maybe/probably typed pointers) and limiting hoisting to subscripting, so it's kind of like the -> operator overload in C++.
https://github.com/genericptr/freepascal/wiki/Default-property
It shouldn't hoist only public members, it should hoist according to the visibility rules (thus the hoisting depends on the callsite), otherwise it won't behave like Pascal classes do and thus we can forget it right away.
So this means if the property is in the private section it looks at private visibility in the parent class? Yeah that's probably right we need to do that.
You should reread the visibility rules of Object Pascal:
- private: identifier is visible inside the whole unit
- strict private: identifier is only visible inside code of the class
- protected: identifier is visible inside the whole unit as well as
inside descendants of the class as well as type helpers
- strict protected: identifier is visible inside code of the class,
inside descendants of the class as well as type helpers
- public: identifier is visible in the whole unit and (if it's declared
in the interface section) any unit that includes that unit
- published: like public, but with RTTI data
Post by Ryan Joseph via fpc-devel
1) What do read/write access even mean in the context of the default properties? The terms don't really make much sense given what the the property does. Right now the property could be read only or write only but those don't really have any affect on the hoisting process itself so it's kind of deceptive. Methods are always "read-only" but i guess you could hoist fields/properties and inherit the access level of the default property. No idea if that's helpful or just adding needless complexity. Any ideas?
Property accessors indeed don't really make sense. Maybe a "default
field" would be better than a "default property".
Post by Ryan Joseph via fpc-devel
2) I also think there needs to be another name for the feature than "default property" since this term is already used for array indexers and could even be used for something like traits in the future (traits would be reusing much of this code). I need to add some enum names and default_property is already used so I need to think of something else.
3) What about allowing type pointers as default properties? This should be possible and is in the spirit of the feature anyways, that is ref counting. We may need to add some additional logic to properties (just internally) so that they can be used with pointers but I'm not sure about that yet.
Pointers are only useful if the ^ "operator" is hoisted as well.

Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin
Ryan Joseph via fpc-devel
2021-05-10 21:18:45 UTC
Permalink
Why should they? You pass the reference to a non-reference counted parameter/field/variable, the reference count is increased and then what? It sits there for the remaining life time of the program, because nothing decrements the reference count?
I see what you mean. The FGL containers also call Finalize though when the container is freed so it does indeed keep balanced.

Lets focus on the record approach for now then. I don't think I know enough to understand where are the pitfalls are.
- private: identifier is visible inside the whole unit
- strict private: identifier is only visible inside code of the class
- protected: identifier is visible inside the whole unit as well as inside descendants of the class as well as type helpers
- strict protected: identifier is visible inside code of the class, inside descendants of the class as well as type helpers
- public: identifier is visible in the whole unit and (if it's declared in the interface section) any unit that includes that unit
- published: like public, but with RTTI data
yes, yes, I know. I thought we'd do something different.
Post by Ryan Joseph via fpc-devel
1) What do read/write access even mean in the context of the default properties? The terms don't really make much sense given what the the property does. Right now the property could be read only or write only but those don't really have any affect on the hoisting process itself so it's kind of deceptive. Methods are always "read-only" but i guess you could hoist fields/properties and inherit the access level of the default property. No idea if that's helpful or just adding needless complexity. Any ideas?
Property accessors indeed don't really make sense. Maybe a "default field" would be better than a "default property".
"Default field" is certainly more unique and thus better. We'll have to think about this more.
Post by Ryan Joseph via fpc-devel
2) I also think there needs to be another name for the feature than "default property" since this term is already used for array indexers and could even be used for something like traits in the future (traits would be reusing much of this code). I need to add some enum names and default_property is already used so I need to think of something else.
3) What about allowing type pointers as default properties? This should be possible and is in the spirit of the feature anyways, that is ref counting. We may need to add some additional logic to properties (just internally) so that they can be used with pointers but I'm not sure about that yet.
Pointers are only useful if the ^ "operator" is hoisted as well.
I meant to say pointers to records so yes the ^. would need to be there. I haven't looked into how this would be implemented but I got it working with classes for now. It would be nice to make pointers to records be possible for smart pointers so I'll look into that later.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-
Sven Barth via fpc-devel
2021-05-11 06:54:17 UTC
Permalink
Post by Ryan Joseph via fpc-devel
Why should they? You pass the reference to a non-reference counted parameter/field/variable, the reference count is increased and then what? It sits there for the remaining life time of the program, because nothing decrements the reference count?
I see what you mean. The FGL containers also call Finalize though when the container is freed so it does indeed keep balanced.
But only if the generic container is indeed specialized with the
refcounted type. If it's e.g. TObject then the whole thing is up in the
air again, because the whole point is that we *don't* want to burden
non-reference counted class types with the reference counting stuff (and
the need to check at runtime whether the type is reference counted or
not *is* a burden).

Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo
Ryan Joseph via fpc-devel
2021-05-16 20:05:08 UTC
Permalink
Post by Ryan Joseph via fpc-devel
Lets focus on the record approach for now then. I don't think I know enough to understand where are the pitfalls are.
This was another thing I wanted off my mind since a couple years ago already so I got a pretty good start of an implementation. Since Sven has made it pretty clear we can't add ARC to Pascal without altering all instances of TObject, this is the next best thing. Together with record management operators this is how we can achieve "smart pointers" in Pascal.

I've constrained the implementation to hoisting the following members:

* Fields (duplicate field names with the record gives errors)
* Properties (last-wins, like in class hierarchies)
* Methods (and overloading with the method itself)
* for..in enumerator so container classes can be used naturally (other operators are not supported to keep the implementation simple for 99% use cases)
* Hoisting happens only by subscripting from the outside, so not within the record using implicit-self.
* Only records + classes are supported types (in the interest of keep the feature for ARC and not other things like nullable types or traits/mix-ins etc...)

https://bugs.freepascal.org/view.php?id=38872

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailm

Ryan Joseph via fpc-devel
2021-05-09 15:14:44 UTC
Permalink
Post by Sven Barth via fpc-devel
=== code begin ===
{$mode objfpc}
type
TTest = class
protected
procedure DoSomething;
end;
TTestSub = class refcounted(TTest)
public
procedure Test;
end;
procedure TTest.DoSomething;
begin
// maybe this functions stores the reference
SomeFuncThatTakesAObject(Self);
end;
procedure TTest.Test;
begin
DoSomething;
end;
=== code end ===
I see, the reference counting is broken because you move up into a non-ref counted class. Yeah that's something programers simply should not do or be prevented from doing. I don't see this particular case being a problem however because your ref counted object is going to be in the base of a hierarchy, probably enforced even. The only reason for opt-in ARC is so we don't pollute TObject but it still doesn't mean that you should be adding this in the middle of class trees.

Here is the bigger problem:

var
list: TObjectList;

procedure HandleObject(obj: TObject);
begin
// the list now stores the class but it's lost ref-counting because it was cast to TObject
list.Add(obj);
end;

var
obj: TTestSub;
begin
HandleObject(obj);
end;

or

var
obj: TObject;
begin
// we lost ref counting now!
obj := TTestSub.Create;
HandleObject(obj);
end;

Once you cast away from your managed class type things fall apart. Records aid this by not allowing casting but you could enforce some kinds of checks for managed classes if you wanted to. Doesn't seem like a deal breaker to me if you add new type rules for passing/assigning.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinf
Ryan Joseph via fpc-devel
2021-05-10 20:22:54 UTC
Permalink
Over the weekend I fixed up my old default property code to work with records only which implement classes (which reduced lots of the complexity). It's actually a pretty clean and small implementation so I put a patch you can look at and try. It's not decided upon but this is a place to start should we decide to go this route for "start pointers".

https://bugs.freepascal.org/view.php?id=38872

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/li
Sven Barth via fpc-devel
2021-05-10 21:09:56 UTC
Permalink
Post by Ryan Joseph via fpc-devel
Post by Sven Barth via fpc-devel
=== code begin ===
{$mode objfpc}
type
TTest = class
protected
procedure DoSomething;
end;
TTestSub = class refcounted(TTest)
public
procedure Test;
end;
procedure TTest.DoSomething;
begin
// maybe this functions stores the reference
SomeFuncThatTakesAObject(Self);
end;
procedure TTest.Test;
begin
DoSomething;
end;
=== code end ===
I see, the reference counting is broken because you move up into a non-ref counted class. Yeah that's something programers simply should not do or be prevented from doing. I don't see this particular case being a problem however because your ref counted object is going to be in the base of a hierarchy, probably enforced even. The only reason for opt-in ARC is so we don't pollute TObject but it still doesn't mean that you should be adding this in the middle of class trees.
But that won't stop users from introducing reference counted classes
somewhere down in the tree. Enabling reference counting by type is
essentially introducing a new class hierarchy and that makes it useless
for interacting with the existing RTL/FCL/LCL.
Post by Ryan Joseph via fpc-devel
var
list: TObjectList;
procedure HandleObject(obj: TObject);
begin
// the list now stores the class but it's lost ref-counting because it was cast to TObject
list.Add(obj);
end;
var
obj: TTestSub;
begin
HandleObject(obj);
end;
or
var
obj: TObject;
begin
// we lost ref counting now!
obj := TTestSub.Create;
HandleObject(obj);
end;
Once you cast away from your managed class type things fall apart. Records aid this by not allowing casting but you could enforce some kinds of checks for managed classes if you wanted to. Doesn't seem like a deal breaker to me if you add new type rules for passing/assigning.
That is exactly *the same* problem, not a "bigger" one. It doesn't
matter if the instance is passed to a function right away or through
using Self in a parent class, the result is the same: the reference
count is no longer accurate.

And *that* is why I'm in favor of an approach that is external to the
class. It's much clearer then that this is not something inherent to the
class and thus users won't expect this to be handled transparently (and
this is also why I'm against a keyword like Michael suggested, it only
wakes expectations that we won't and can't fullfill).

Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/m
Sven Barth via fpc-devel
2021-05-07 05:40:26 UTC
Permalink
Post by Ryan Joseph via fpc-devel
There is a performance penalty when using them, which one reason why the compiler sources don't use them. There's probably other reasons too. There might be some speed-up potential where standard Exit calls are concerned, but I'm not sure.
I just did a search and I did indeed see a few try..finally blocks but not many. As I understand it there is a "finalization" section of each procedure which is used for ref counted objects so I assumed the statement was merely moved to that location but I guess there's some concerns over exceptions.
There is no "finalization" section. It's really just an implicit try ...
finally block that the compiler inserts. Look for
"cs_implicit_exceptions" and "pi_needs_implicit_finally" if you want to
learn more.
Post by Ryan Joseph via fpc-devel
Either way looking at the programming language landscape the better way forward seems to be some opt-in ARC for TObject but I don't know if the compiler team is receptive to that (Sven made some attempt years ago but abandoned it). it's kind of frustrating that we have ref counted types but that isn't extended to classes. Hopefully that's something we can tackle one of these days...
The problem is that the whole class tree needs to support it for it to
work correctly even if it's not the whole hierarchy that has as it
enabled. That means at least one additional field inside TObject that a)
controls that behavior and b) contains the reference count. This means
that *all* class instances increase by a LongInt. This might not sound
like much, but FPC also allows to work on smaller systems (and I don't
mean the really small embedded ones as those don't have TObject enabled
anyway) and there an additional LongInt for each instance might be critical.

If the reference count feature is optional (in addition to the above)
then an open question is what would happen if such a reference counted
instance is assigned to a non-reference counted one. This would need to
take into account all ways such an instance or type can be used
including TClass.

If the reference count would be enabled by default for *all* instance
(like Delphi did in its platform compilers but which they abandoned now)
then you'd have a huge backwards compatibility problem, because there
definitely are cycles out there and thus this option would be an
absolute no-no.

In my opinion the better solution is to continue the road that Maciej
started and to implement that "default field" concept together with
operator hoistening so that records with management operators can be
used as containers. This is essentially the way it's done in C++ as well
(e.g. we use that extensively at work), but it needs some questions
solved for the default field functionality. This way the functionality
is definitely optional and can be controlled per-instance instead of
per-type. What it wouldn't solve however would be the assignment
problems ("wrapped" to non-"wrapped" instance) though that could be
probably be more or less solved by only allowing an explicit conversion
to the non-"wrapped" instance.

Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freep
Michael Van Canneyt via fpc-devel
2021-05-07 06:14:18 UTC
Permalink
Post by Sven Barth via fpc-devel
In my opinion the better solution is to continue the road that Maciej
started and to implement that "default field" concept together with
operator hoistening so that records with management operators can be
used as containers. This is essentially the way it's done in C++ as well
(e.g. we use that extensively at work), but it needs some questions
solved for the default field functionality. This way the functionality
is definitely optional and can be controlled per-instance instead of
per-type. What it wouldn't solve however would be the assignment
problems ("wrapped" to non-"wrapped" instance) though that could be
probably be more or less solved by only allowing an explicit conversion
to the non-"wrapped" instance.
I thought it was agreed at the time that this was the most viable way
forward ?

IIRC there was also the proposal that this could be done automatically using
a keyword:

var
SomeClass : TSomeClass; dispose;

The compiler can internally create the management record with a single default
field and the needed management operator, so the user does not need
to create all that.

I cannot speak for others, but I think 90% of potential use cases for ref counting
would be covered like this in my code: objects that only live inside a
procedure.

Michael.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://li
Sven Barth via fpc-devel
2021-05-07 08:46:47 UTC
Permalink
Post by Michael Van Canneyt via fpc-devel
Post by Sven Barth via fpc-devel
In my opinion the better solution is to continue the road that Maciej
started and to implement that "default field" concept together with
operator hoistening so that records with management operators can be
used as containers. This is essentially the way it's done in C++ as well
(e.g. we use that extensively at work), but it needs some questions
solved for the default field functionality. This way the functionality
is definitely optional and can be controlled per-instance instead of
per-type. What it wouldn't solve however would be the assignment
problems ("wrapped" to non-"wrapped" instance) though that could be
probably be more or less solved by only allowing an explicit conversion
to the non-"wrapped" instance.
I thought it was agreed at the time that this was the most viable way
forward ?
As far as I remember there wasn't really any agreement.
Post by Michael Van Canneyt via fpc-devel
IIRC there was also the proposal that this could be done automatically using
var
SomeClass : TSomeClass; dispose;
The compiler can internally create the management record with a single default
field and the needed management operator, so the user does not need
to create all that.
I'm not aboard with such a keyword. The compiler should provide the
necessary language mechanisms (default field, operator hoisting) and then
there should be a default implementation as part of the RTL. There is no
need to hide this behind a keyword, attribute or whatever.
Post by Michael Van Canneyt via fpc-devel
I cannot speak for others, but I think 90% of potential use cases for ref counting
would be covered like this in my code: objects that only live inside a
procedure.
I think the same.

Regards,
Sven
Michael Van Canneyt via fpc-devel
2021-05-07 09:08:48 UTC
Permalink
Post by Sven Barth via fpc-devel
Post by Michael Van Canneyt via fpc-devel
I thought it was agreed at the time that this was the most viable way
forward ?
As far as I remember there wasn't really any agreement.
Can be, I was not sure... I remember this path was investigated.
Post by Sven Barth via fpc-devel
Post by Michael Van Canneyt via fpc-devel
IIRC there was also the proposal that this could be done automatically using
var
SomeClass : TSomeClass; dispose;
The compiler can internally create the management record with a single default
field and the needed management operator, so the user does not need
to create all that.
I'm not aboard with such a keyword. The compiler should provide the
necessary language mechanisms (default field, operator hoisting) and then
there should be a default implementation as part of the RTL. There is no
need to hide this behind a keyword, attribute or whatever.
There is:

Using a keyword, the compiler could also optimize things by simply initializing
and freeing the instance if the usage of the object in the procedure allows it;
it would allow to skip the record and operators and whatnot.

I'm not proposing to abolish the record approach, just an additional automatism
that will use it, but allows to skip it for simple enough cases which are
probably the largest use-case.

Don't forget that the record/management operator approach will again blow up
binary size; for every variable declared like this you risk creating a new type.

The introduction of generics and their abundant use in Delphi has noticably
slowed down the compiler and increased binary sizes.
To my dismay, compile times of 20 seconds up to 2 minutes have become not
uncommon in Delphi. Something almmost unthinkable in D7 days.

If that can be avoided by use of a keyword for 90% of use cases,
I think it is worth thinking about it and not dismissing it offhand.

Michael.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https:/
Ryan Joseph via fpc-devel
2021-05-07 15:39:08 UTC
Permalink
The introduction of generics and their abundant use in Delphi has noticably slowed down the compiler and increased binary sizes. To my dismay, compile times of 20 seconds up to 2 minutes have become not uncommon in Delphi. Something almmost unthinkable in D7 days.
Is it badly optimized or just overused? I don't have any way to compare but FPC seems pretty good with generics (Sven will know if there are any optimizations that could be made). Implicit function specialization will appear (at least at first) without any optimizations but I'll fix that later.
If that can be avoided by use of a keyword for 90% of use cases, I think it is worth thinking about it and not dismissing it offhand.
I already raised the idea of an "auto" keyword some years ago already and it got rejected in favor of record operators.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepasca
Benito van der Zander via fpc-devel
2021-05-07 15:40:46 UTC
Permalink
Hi,
Post by Michael Van Canneyt via fpc-devel
Don't forget that the record/management operator approach will again
blow up binary size; for every variable declared like this you risk
creating a new type.
the classic Delphi way was to use an interface for freeing. It only
requires one type and nothing blows up.

type
  TDefer = class(TInterfacedObject)
    toDefer: TObject;
    constructor Create(anobject: tobject);
    destructor destroy; override;
  end;

constructor TDefer.Create(anobject: tobject);
begin
  toDefer := anobject;
end;

destructor TDefer.destroy;
begin
  toDefer.free;
  inherited destroy;
end;

function deferedFree(obj: TObject): IUnknown;
begin
  result := TDefer.Create(obj);
end;

Then it can be used as:

procedure test;
var
  sl: TStringList;
begin
  sl := TStringList.Create;
  deferedFree(sl);
  writeln(sl.count);
end;


Unfortunately it fails in FreePascal because the interface is released
too soon.




At worst, one could use a temporary variable in Delphi:

procedure test;
var
  sl: TStringList;
begin
  sl := TStringList.Create;
  var temp := deferedFree(sl);
  writeln(sl.count);
end;
Post by Michael Van Canneyt via fpc-devel
The introduction of generics and their abundant use in Delphi has
noticably slowed down the compiler and increased binary sizes. To my
dismay, compile times of 20 seconds up to 2 minutes have become not
uncommon in Delphi. Something almmost unthinkable in D7 days.
With these generics they copied all the problems of C++. One of the
worst ways of doing that

It would have been better to implement them like dynamic arrays. The
generic code gets RTTI, the specialization does not generate any code,
and just avoids explicit casting.


Cheers,
Benito
Post by Michael Van Canneyt via fpc-devel
Post by Sven Barth via fpc-devel
Post by Michael Van Canneyt via fpc-devel
I thought it was agreed at the time that this was the most viable way
forward ?
As far as I remember there wasn't really any agreement.
Can be, I was not sure... I remember this path was investigated.
Post by Sven Barth via fpc-devel
Post by Michael Van Canneyt via fpc-devel
IIRC there was also the proposal that this could be done automatically using
var
   SomeClass : TSomeClass; dispose;
The compiler can internally create the management record with a single default
field and the needed management operator, so the user does not need
to create all that.
I'm not aboard with such a keyword. The compiler should provide the
necessary language mechanisms (default field, operator hoisting) and then
there should be a default implementation as part of the RTL. There is no
need to hide this behind a keyword, attribute or whatever.
Using a keyword, the compiler could also optimize things by simply
initializing and freeing the instance if the usage of the object in
the procedure allows it; it would allow to skip the record and
operators and whatnot.
I'm not proposing to abolish the record approach, just an additional
automatism that will use it, but allows to skip it for simple enough
cases which are
probably the largest use-case.
Don't forget that the record/management operator approach will again
blow up binary size; for every variable declared like this you risk
creating a new type.
The introduction of generics and their abundant use in Delphi has
noticably slowed down the compiler and increased binary sizes. To my
dismay, compile times of 20 seconds up to 2 minutes have become not
uncommon in Delphi. Something almmost unthinkable in D7 days.
If that can be avoided by use of a keyword for 90% of use cases, I
think it is worth thinking about it and not dismissing it offhand.
Michael.
_______________________________________________
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Ryan Joseph via fpc-devel
2021-05-07 16:21:00 UTC
Permalink
the classic Delphi way was to use an interface for freeing. It only requires one type and nothing blows up.
That's clever but even try..finally is less overhead.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listin
Sven Barth via fpc-devel
2021-05-08 14:03:25 UTC
Permalink
Post by Benito van der Zander via fpc-devel
Post by Michael Van Canneyt via fpc-devel
The introduction of generics and their abundant use in Delphi has
noticably slowed down the compiler and increased binary sizes. To my
dismay, compile times of 20 seconds up to 2 minutes have become not
uncommon in Delphi. Something almmost unthinkable in D7 days.
With these generics they copied all the problems of C++. One of the
worst ways of doing that
It would have been better to implement them like dynamic arrays. The
generic code gets RTTI, the specialization does not generate any code,
and just avoids explicit casting.
That would also be a much less flexible approach. The way it is now
allows for much more functionality to be covered with generics.

Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org
Luis Henrique via fpc-devel
2021-05-07 12:56:52 UTC
Permalink
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Ryan Joseph via fpc-devel
2021-05-07 15:57:27 UTC
Permalink
Post by Michael Van Canneyt via fpc-devel
I cannot speak for others, but I think 90% of potential use cases for ref counting
would be covered like this in my code: objects that only live inside a
procedure.
I think the same.
There's also a function which returns dynamically allocated memory but doesn't intend for you to keep it around very long. Cocoa/Objective-C has a good system for this which is an "autorelease pool" and while it's not language construct but rather a runtime system it does rely on an "Any" type (called id in Objective-C) so that methods can be chained together like this:

a := TArray.Create([1,2,3]).AutoRelease;

We can't do this in Pascal because the AutoRelease functions return type is not compatible with the type of the caller. Could we add something like an "Any" return type to Pascal which is a type that is compatible with any class? This relies on an event loop that can capture this garbage and dispose of it but it goes a LONG way in helping with common memory management problems.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-b
Sven Barth via fpc-devel
2021-05-08 14:05:39 UTC
Permalink
Post by Ryan Joseph via fpc-devel
Post by Michael Van Canneyt via fpc-devel
I cannot speak for others, but I think 90% of potential use cases for ref counting
would be covered like this in my code: objects that only live inside a
procedure.
I think the same.
a := TArray.Create([1,2,3]).AutoRelease;
We can't do this in Pascal because the AutoRelease functions return type is not compatible with the type of the caller. Could we add something like an "Any" return type to Pascal which is a type that is compatible with any class? This relies on an event loop that can capture this garbage and dispose of it but it goes a LONG way in helping with common memory management problems.
Dynamic arrays are reference counted, thus you don't need to do any
manual memory management on them.

Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mail
Ryan Joseph via fpc-devel
2021-05-08 17:27:26 UTC
Permalink
Post by Ryan Joseph via fpc-devel
a := TArray.Create([1,2,3]).AutoRelease;
We can't do this in Pascal because the AutoRelease functions return type is not compatible with the type of the caller. Could we add something like an "Any" return type to Pascal which is a type that is compatible with any class? This relies on an event loop that can capture this garbage and dispose of it but it goes a LONG way in helping with common memory management problems.
Dynamic arrays are reference counted, thus you don't need to do any manual memory management on them.
That was a bad example. It's for ANY class really.

o := TObject.Create.AutoRelease;

Then next event cycle the autorelease pool frees all the objects added to it. Very simple but effective however we can't do this in Pascal without a new permissive return type.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists
Kostas Michalopoulos via fpc-devel
2021-05-10 23:59:03 UTC
Permalink
Post by Ryan Joseph via fpc-devel
That was a bad example. It's for ANY class really.
o := TObject.Create.AutoRelease;
Then next event cycle the autorelease pool frees all the objects added to it. Very simple but effective however we can't do this in Pascal without a new permissive return type.
You do not need any special language feature for that, you can simply do
something like

ReleaseLater(TObject.Create)

and have ReleaseLater and ReleaseQueuedObjects procedures in a shared
unit like

procedure ReleaseLater(Obj: TObject);
procedure ReleaseQueuedObjects;

and have ReleaseQueuedObjects called on app idle. LCL already has
something like that with TApplication.ReleaseComponent but, as the name
implies, it is only for TComponent instances. However nothing prevents
you from making your own, it is just a few lines of code and certainly
an app with flow complex enough to need such a construct wont be
bothered by a few additional lines for this.

You could even use type helpers to make it look like a method :-P.

Kostas

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https:/
Ryan Joseph via fpc-devel
2021-05-11 01:09:51 UTC
Permalink
You do not need any special language feature for that, you can simply do something like
ReleaseLater(TObject.Create)
yes but we can't get back the reference. It's a small thing but making this possible as return type means we can chain the calls together and make it a one line statement. It's just a nice thing from Objective-C which we use heavily to manage memory and it works very well.

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/
Kostas Michalopoulos via fpc-devel
2021-05-11 02:21:41 UTC
Permalink
Post by Ryan Joseph via fpc-devel
You do not need any special language feature for that, you can simply do something like
ReleaseLater(TObject.Create)
yes but we can't get back the reference. It's a small thing but making this possible as return type means we can chain the calls together and make it a one line statement. It's just a nice thing from Objective-C which we use heavily to manage memory and it works very well.
How about function ReleaseLater(Obj: TObject): TObject that simply
returns Obj? (though TBH i can't say i am a fan of these chains from a
readability perspective).

Kostas
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/f
Ryan Joseph via fpc-devel
2021-05-07 16:11:19 UTC
Permalink
In my opinion the better solution is to continue the road that Maciej started and to implement that "default field" concept together with operator hoistening so that records with management operators can be used as containers. This is essentially the way it's done in C++ as well (e.g. we use that extensively at work), but it needs some questions solved for the default field functionality. This way the functionality is definitely optional and can be controlled per-instance instead of per-type. What it wouldn't solve however would be the assignment problems ("wrapped" to non-"wrapped" instance) though that could be probably be more or less solved by only allowing an explicit conversion to the non-"wrapped" instance.
If you remember I already started this but it got WAY out of hand and I had to basically abandon all my work on it because it was such a mess. The problem was we opened the possibility of default properties being used on any type (could be used for nullable types even) and this was in my opinion a mistake (at least for now).

I'm not 100% behind the default implements property Sven proposed for traits but it shares most of the functionality with the default property so maybe we should figure out how to constrain the default property and work on that angle. If I do continue on to the traits this code will be shared anyways.

What I learned is that hoisting the following members is very easy to implement:

- Methods
- Fields
- Properties

and these are much more complicated and had a massive code footprint on the compiler:

- Operators
- Assignments
- Array indexing
- Implicit conversions (like if, while statements etc... these where needed for nullable types but not for ARC)

From my experience I would say we ONLY allow Methods/Properties and MAYBE fields and then ignore all the other stuff. Default properties can ONLY be a class type now. There is already an := operator the record can implement and it works with management operators already. This covers 90% of the use cases for memory management and doesn't introduce all sorts of unpredictable stuff.

What about that?

Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://l
Ryan Joseph via fpc-devel
2021-05-07 16:41:10 UTC
Permalink
There is no "finalization" section. It's really just an implicit try ... finally block that the compiler inserts. Look for "cs_implicit_exceptions" and "pi_needs_implicit_finally" if you want to learn more.
Does that mean if you disable implicit exceptions then ALL ref counted types leak memory? I could swear there was some post-routine code did all the cleanup stuff and called finalization* on different types. That's how record operators worked I thought and so i thought a defer keyword could simply hook into that system.
Post by Ryan Joseph via fpc-devel
Either way looking at the programming language landscape the better way forward seems to be some opt-in ARC for TObject but I don't know if the compiler team is receptive to that (Sven made some attempt years ago but abandoned it). it's kind of frustrating that we have ref counted types but that isn't extended to classes. Hopefully that's something we can tackle one of these days...
The problem is that the whole class tree needs to support it for it to work correctly even if it's not the whole hierarchy that has as it enabled. That means at least one additional field inside TObject that a) controls that behavior and b) contains the reference count. This means that *all* class instances increase by a LongInt. This might not sound like much, but FPC also allows to work on smaller systems (and I don't mean the really small embedded ones as those don't have TObject enabled anyway) and there an additional LongInt for each instance might be critical.
That can never be an option to blow up TObject. I figured there could be something like a $M+ switch that would compile a class with ref counting. Then in the RTTI units we would simply have an entry for this new class type which had initialize/finalize/addref etc... functions. I saw this for record operators and dynamic arrays so I thought the system could be extended to classes.
If the reference count feature is optional (in addition to the above) then an open question is what would happen if such a reference counted instance is assigned to a non-reference counted one. This would need to take into account all ways such an instance or type can be used including TClass.
That doesn't sound like a problem to me but I haven't thought about it deeply. If it was confusing there could be a new var section to denote ARC objects:

var
someClass: TMyObject;
retained
someClass: TMyObject;
begin
end;

{$RETAINED+}
type
TMyObject = class
private
someClass: TMyObject;
public retained
someClass: TMyObject;
otherClass: TObject; // this would give an error because class is not compiled for ARC
end;
{$RETAINED-}


Regards,
Ryan Joseph

_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailm
Jonas Maebe via fpc-devel
2021-05-08 08:58:58 UTC
Permalink
Post by Ryan Joseph via fpc-devel
Does that mean if you disable implicit exceptions then ALL ref counted types leak memory?
Only if an exception occurs.


Jonas
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fp
Sven Barth via fpc-devel
2021-05-07 05:26:42 UTC
Permalink
Post by Ryan Joseph via fpc-devel
Other than that, you're right and what Ryan is trying to do is definitely the intended purpose of try ... finally.
Is there any runtime code involved with try..finally or does it just reorganize the code to run at the end of the block? My understanding of the defer keyword is that is was just a fancy way to move some code into a block which always gets run with a function exits.
It depends on the platform. In general FPC uses setjmp/longjmp based
exception handling, thus there is a slight penalty in setting up the
exception frame (no matter if "finally" or "except").

In case of Win32 and Win64 the OS' Structured Exception Handling
functionality is used. On i386-win32 there is still a runtime overhead
due to how exceptions are managed, but on x86_64-win64 and aarch64-win64
(and in theory also arm-wince and arm-win32, but we have no ARM support
for SEH yet (and arm-win32 isn't implemented yet :P )) this is done
through meta data located in the PE file (the .pdata and .xdata
sections) which allows for an implicit setup of the frames and thus
there'll only be a penalty if an exception occurrs (cause the OS and the
exception handling code will have to parse that data).

Trunk also supports POSIX exceptions on selected *nix based systems,
though I haven't looked in depth yet in how far they incur a runtime
penalty (also they need to be enabled by enabling them in the compiler
and then recompiling everything, cause they're still experimental).

Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://
Jonas Maebe via fpc-devel
2021-05-08 09:01:37 UTC
Permalink
Post by Sven Barth via fpc-devel
Trunk also supports POSIX exceptions on selected *nix based systems,
though I haven't looked in depth yet in how far they incur a runtime
penalty (also they need to be enabled by enabling them in the compiler
and then recompiling everything, cause they're still experimental).
They're not POSIX, but DWARF-EH. They don't incur any cost except if an
exception is triggered (but then the cost is quite high).

They're also the only supported exception type when using the LLVM
backend (which is why adding Windows-support is more work than adding
support for other platforms to the LLVM backend, as even at the LLVM IR
level Windows exceptions are modeled differently).


Jonas
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
https://lists.f
Continue reading on narkive:
Loading...