vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
class Mal.Env : GC.Object {
|
|
|
|
private GLib.HashTable<weak Mal.Sym, weak Mal.Val> data;
|
|
|
|
weak Mal.Env? outer;
|
2019-05-10 11:34:20 +03:00
|
|
|
|
|
|
|
construct {
|
vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
data = new GLib.HashTable<weak Mal.Sym, weak Mal.Val>(
|
2019-05-10 11:34:20 +03:00
|
|
|
Mal.Hashable.hash, Mal.Hashable.equal);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Env.within(Mal.Env outer_) {
|
|
|
|
outer = outer_;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Env() {
|
|
|
|
outer = null;
|
|
|
|
}
|
|
|
|
|
vala: implement a garbage collector.
This fixes a lot of memory leakage due to me having relied on the Vala
reference-counting system to do my memory management.
The most obvious bad result of that design decision was the memory
leak from Mal.Nil pointing to itself as metadata. But fixing that
didn't solve the whole problem, because other kinds of reference cycle
are common.
In particular, the idiom `(def! FUNC (fn* (ARGS) BODY))`, for defining
a function in the most obvious way, would create a cycle of two
objects: from the outer environment in which FUNC is defined, to the
function object for FUNC itself, back to that same environment because
it was captured by FUNC.
And _either_ of those objects could end up being the only element of
the cycle referred to from the rest of the system: it could be the
environment, if nothing ever uses that function definition, or it
could be the function, if that function object is looked up and
returned from an outer function that was the last user of the
environment. So you can't break the cycle in the way that reference
counting systems would like you to, by making a well-chosen one of the
links weak: there's no universally right choice for which one it needs
to be.
So I've fixed it properly by writing a simple garbage collector. In
Vala's ref-counted environment, that works by being the only thing
allowed to hold an _owning_ reference to any derivative of GC.Object.
All the normal kinds of link between objects are now weak references;
each object provides a gc_traverse method which lists all the things
it links to; and when the garbage collector runs, it unlinks any
unwanted objects from its big linked list of all of them, causing the
one owned reference to each one to disappear.
Now the perf3 test can run without its memory usage gradually
increasing.
2019-05-12 12:05:34 +03:00
|
|
|
public override void gc_traverse(GC.Object.VisitorFunc visit) {
|
|
|
|
visit(outer);
|
|
|
|
foreach (var key in data.get_keys()) {
|
|
|
|
visit(key);
|
|
|
|
visit(data[key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-10 11:34:20 +03:00
|
|
|
public Env.funcall(Mal.Env outer_, Mal.Listlike binds, Mal.List exprs)
|
|
|
|
throws Mal.Error {
|
|
|
|
outer = outer_;
|
|
|
|
var binditer = binds.iter();
|
|
|
|
unowned GLib.List<Mal.Val> exprlist = exprs.vs;
|
|
|
|
|
|
|
|
while (binditer.nonempty()) {
|
|
|
|
var paramsym = binditer.deref() as Mal.Sym;
|
|
|
|
if (paramsym.v == "&") {
|
|
|
|
binditer.step();
|
|
|
|
var rest = binditer.deref();
|
|
|
|
binditer.step();
|
|
|
|
if (rest == null || binditer.nonempty())
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"expected exactly one parameter name after &");
|
|
|
|
set(rest as Mal.Sym, new Mal.List(exprlist.copy()));
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
if (exprlist == null)
|
|
|
|
throw new Mal.Error.BAD_PARAMS(
|
|
|
|
"too few arguments for function");
|
|
|
|
set(paramsym, exprlist.data);
|
|
|
|
binditer.step();
|
|
|
|
exprlist = exprlist.next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (exprlist != null)
|
|
|
|
throw new Mal.Error.BAD_PARAMS("too many arguments for function");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use the 'new' keyword to silence warnings about 'set' and 'get'
|
|
|
|
// already having meanings that we're overwriting
|
|
|
|
public new void set(Mal.Sym key, Mal.Val f) {
|
|
|
|
data[key] = f;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Mal.Env? find(Mal.Sym key) {
|
|
|
|
if (key in data)
|
|
|
|
return this;
|
|
|
|
if (outer == null)
|
|
|
|
return null;
|
|
|
|
return outer.find(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
public new Mal.Val get(Mal.Sym key) throws Mal.Error {
|
|
|
|
var found = find(key);
|
|
|
|
if (found == null)
|
|
|
|
throw new Error.ENV_LOOKUP_FAILED("'%s' not found", key.v);
|
|
|
|
return found.data[key];
|
|
|
|
}
|
|
|
|
}
|