Closed renehiemstra closed 3 months ago
I generally like the idea, but (at least) your __move
returns an object, which is then copied by value by terra. If I read that correctly, this means stuff like offset_ptr
s (container that saves the offset between this
-ptr and the value, VERY useful for IPC stuff) are not possible, so maybe your move and copy could take a second (first?) parameter in which to move/copy the object into? Another example would be memory reuse in containers, as fully destroying the object and constructing a new one is far more costly than just reassigning the content, so an optimized move/copy assignment would be appreciated.
Also: I first thought, that you do not have a method for move assignment (of existing variables), but it's of course possible to make an assign
method, so maybe elaborate in that direction to avoid confusion when skimming over the PR?
Also Also: Your RAII code does not do any memory allocation, only the not-shared_ptr test script. Maybe clarify on that topic, to avoid stirring up any memory management debates
Also Also Also: you commented out some sdkroot
platform code, probably so it works on your machine, but this shouldn't be part of the PR
__move
__move
metamethod to return from a function and to get around the deferred destructor call to __dtor
. I can investigate some ideas to do this in a better way, such that a user-defined __move
method and an additional copy is not needed.offset_ptr
type .@
operator. So if we want pointer semantics then we could think about enabling this?__copy
copy
trait enables specialized behavior (for instance a bit-copy). In Terra we would do the opposite (to avoid breaking compatibility), a bit-copy by default and a __copy(self : &A, other : &A)
metamethod could be called to induce specialized behavior (you could implement a move assignment here). What do you think about this?__init
It may be worthwhile to have a default __init
that sets pointers to nil
whenever a __dtor
is implemented.
Also Also: The point of the contribution is to add some metamethods to enable RAII, without introducing breaking changes. This leaves memory allocation using the stdlib C library in the hands of the programmer.
Also Also Also: right, the commented out platform code is a separate issue that I forgot to undo.
As I said on Zulip, I appreciate the work that @renehiemstra has put into this and agree that something along these lines would be useful.
Unfortunately I am continuing to run out of time to actually look at this in depth.
I think it would be nice to make sure that this can capture basic patterns similar to std::vector
, std::string
, etc. I suppose at a superficial level those are similar to smart pointers, but one would generally expect (a) owning semantics, not shared semantics, and (b) copies are especially painful if we don't optimize those.
I have enabled the CI to run so that you can at least see those results. I may have to re-enable it on each push since this is a first-time contribution.
Edit: I also wanted to check that the metaprogramming APIs are compositional: i.e., you can create a vector(vector(int))
or a vector(string)
or etc. and have it do the right thing. I'm not saying the current version isn't, but I just haven't had time to think through it.
I was able to implement a __dtor
metamethod (without using defer
) that is invoked at the end of each new scope, before a possible return-statement, and only acts on the variables that are not returned. As such, destruction is really tied to the lifetime of an object, which is precisely what we need. The __move
metamethod and the additional copy are no longer needed.
I also implemented a __copy
metamethod that takes two arguments
A.metamethods.__copy(from : &A, to : &B)
and / or
A.metamethods.__copy(from : &B, to : &A)
The arguments of __copy
can be different and it can be an overloaded function.
It can also be used on object construction,
var a : A
a.data = ...
var b = a
which is the same as writing
var a : A
a.data = ...
var b : A
b = a
The __init
metamethod is called in object initialization, if it is implemented.
I think the above covers all the use cases of smart containers and pointers in c++ (e.g. std::vector
, std::unique_ptr
, and std::shared_ptr
) and it should also work for the case of boost::offset_ptr
I still need to make the metamethods work properly in the case of compositional API's, like you mentioned (vector(vector(int))
or a vector(string)
).
Also, I need to handle some corner cases. In Terra its allowed to combine a logical statement in a return statment, e.g.
return some_bool_flag and x or y
I am not sure how to deal with these yet, because this is runtime information.
Reopened in #659
This pull request adds memory management to terra using the concept of RAII.
The design adds the following four metamethods to the language that enable the implementation of smart (shared) pointer types:
__init(self : &A)
__move(self : &A)
__copy(self : &A)
__dtor(self : &A)
If implemented, these methods are inserted judiciously during the type checking phase, implemented in
terralub.lua
. All these metamethods can be implemented as macro's or as terra functions.The design does not introduce any breaking changes. No new keywords are introduced. Heap resources are acquired and released using the regular C stdlib functions such
malloc
andfree
.I will explain the metamethods in some more depth:
__init
is used to initialize pointer variables to nil, making them safe to delete. The implementation checks for an init metamethod in any defvar statement:var a : A
and applies it if it exists. If there is an initializer,var a : A = ...
then the implementation does not search for an `initmetamethod. A deferred call to
dtoris added in this case and a call to
copy` if the right-hand-side involves a struct variable.__dtor
is used to free heap memory. A defer node to this metamethod is added during type-checking such that__dtor
is called at the end of a scope.__dtor
is also applied before any assignment, e.g.a = alloc(...)
, to free old resources.__move
is needed in order to return from a function. It moves resources to a temporary variable , and sets the old pointer variable tonil
such that the deferred destructor call does not free the heap resources.__move
will be called on every returned variable (that implements it) in a return statement.__copy
enables side effects in a statementvar b = a
orb = a
, such as increasing a reference counter. It acts solely on the right-hand-side variable in the assignment expression.I have added a test file smartptr.t in the tests folder. It contains an implementation of a smart shared pointer and two integration tests that test all four metamethods. These tests print out the control flow of the program to check when each meta method is invoked.
All existing tests in the tests folder pass on my machine (except for a few that did not pass prior to the changes)
If the design is accepted, there are a few things left to do:
prettystring
in terralib.lua.I am happy to work further on improving the design where needed.