Objects with non-primitive fields #52
Labels
No Milestone
No Assignees
3 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: M-Labs/nac3#52
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
For objects with non-primitive fields, if we do
alloca
inside the constructor, the fields will become dangling pointers once we exit the constructor.Example:
I can think of three ways to fix this:
__init__
function, allocate them in the caller. Note that the field pointer list should include all allocations needed in the constructor, not only the object's fields, but fields of fields etc.@sb10q What do you think?
I suppose those
__init__
and classes should be decorated with@kernel
.Inlining the constructors (1) seems fine and something not too unusual (many existing compilers have ways to force inlining of functions).
The others seem complicated - do they have clear advantages?
Inlining would not support recursive references, and the instructions required can be exponential (consider A contains 2 B, B contains 2 C, etc.).
I agree inlining is not a satisfactory solution. We'd still have the problem for all other functions, and there's no particular reason to have special treatment for constructors. E.g.:
In my opinion, the best way to solve this is to do a quick static analysis to know where things should be allocated. Even if we don't go all the way to proper region-based memory management, we can still do a simplified region analysis that would tell us, here, that since it's stored in
self.foo
, theFoo(a)
allocation should outlive the call tobaz
(or__init__
).To keep it simple and avoid having to support dynamic regions (since we don't want to change the runtime for now), we could require that there should be a statically bounded number of escaping allocations – so all allocations performed in loops should remain strictly local to the loop.
Each function would take an array
arr
of adresses as a parameter, write its first escaping value intoarr[0]
, etc. The analysis would keep track of the assigned stack regions of all manipulated data and their fields, allocating the stack spaces in the right places, and passes the corresponding addresses through the array parameter.Recursive functions containing escaping allocations would be disallowed, similarly to loops.
I don't see how the "tail-recursion through trampolines" idea would work. Trampolines require allocating some sort of closure capturing the current context, so that does not really seem to solve the problem.
On the other hand, what we could do is a CPS transformation of the whole program. Assuming users don't use non-tail recursive functions, this would allow allocating everything on the stack safely. But we'd still need to make sure allocations performed in loops don't leak out of the loop. And making the CPS code perform well would require a later defunctionalization pass, which is complictaed to implement. So I don't think it's a very attractive option.
@LPTK Strictly speaking you are correct. But creating objects on the device is a bit of an advanced feature (old compiler doesn't support it at all) and restrictions such as being able to store them in fields only in constructors would be acceptable.
Anyway we can explore what you propose.
See related issue: #51
if constructed via constructor: local object
otherwise: non-local object (including object fields)
for non-local objects: prohibit assigning non-global objects to non-local object's fields