HuoLanguage / huo

interpreted language written in C
MIT License
212 stars 21 forks source link

References and memory leaks #55

Open TheLoneWolfling opened 8 years ago

TheLoneWolfling commented 8 years ago

So. I've been puttering around starting to deal with making sure that Huo doesn't leak memory like a sieve, and have hit upon a problem. Namely, arrays.

It's generally pretty clear when to free a value. However, since arrays in values are referenced by reference, instead of by value, one can have multiple references to an array. This means that we cannot just always free the array when freeing a value (of type array).

Normally, this is dealt with in one of two ways. Either through reference counting, or via garbage collection. However, given that you want performance, neither is particularly appealing.

On a very related note, how do you wish the following to work? Or rather, what do you want it to print?

(let x [])
(let y x)
(push y 1)
(print x)

Should it print [1]? Or []?

TheLoneWolfling commented 8 years ago

We can, I suppose, limit refcounting to arrays only.

TheLoneWolfling commented 8 years ago

Copying in @incrediblesound's https://github.com/HuoLanguage/huo/issues/52#issuecomment-232427437:

@TheLoneWolfling that relates to your question in #55. I think we should opt for immutable arrays. So:

(let x [])
(let y x) ; y is now a copy of x
(push 1 y) ; y = [1] and x = []
(let z [1, 2, 3])
(let a (map z item index (* item item)))
; z = [1, 2, 3] and y = [1, 4, 9]

But that makes me wonder, should we allow the simple assignment of arrays at all? Perhaps that should throw an error, especially since copying nested arrays would be costly. Maybe we can have a copy function to make it explicit:

(let x [1, 2, 3])
(let y (copy x))

Does that sound good to you @TheLoneWolfling ?

TheLoneWolfling commented 8 years ago

And my response:

That could work. Call and reference by value, in other words.

(Though note that what you are suggesting is not immutable arrays. If it was, (push 1 y) would return a new array with 1 pushed to it, and y wouldn't have changed.)

If you have something like this:

(f (index (index (index x 1) 2) 1))
(g (index (index (index x 1) 2) 2))
(h (index (index (index x 1) 2) 3))

You cannot pull the common index subexpression out into a let definition, which could be annoying.

I'd prefer not to require a copy function, though. It'd end up being used so often that it'd just end up being syntactic noise.


I don't see how this deals with #55, however.

Consider what happens when you pass an array to this:

(def f x (g x x))

where g is another function. You'll end up with multiple references to the same array, which is the exact problem we're trying to avoid. Unless you're talking about making an array copy at every function call for every array parameter, which would be absurd (and wouldn't allow you to do many things in functions, to boot).

TheLoneWolfling commented 8 years ago

Your thoughts, @incrediblesound?

incrediblesound commented 8 years ago

I'll try to get to this today, been a little busy of late :-/

TheLoneWolfling commented 8 years ago

That's fine, and I understand. I have been busy too (read: looking for work).