Right now relay leak memory for all of the executor (aot, interpreter, vm), for even the simplest program (id = fun x => x). They all happened to be caused by the exact same problem.
In Relay Implementation, a closure capture over all the free variables, holding a reference to all of them. However, it also hold a reference of itself (as long as it has a name, while ANF give everything a name). This make all closure cyclic data structure.
I propose to solve it by making the two following modification:
add a new Value, RecFunc. RecFunc hold a shared_ptr of a vector of closure, and a size_t index into it.
Every Closure will now have another field, a Map<Var, size_t> which store all recursive call (if any, including mutually recursive one) from the parent RecFunc.
This is in inspiration from https://cs.indiana.edu/ftp/techreports/TR73.pdf -

I will submit a PR soon, issuing here for visibility so ppl can avoid the same problem again.
Right now relay leak memory for all of the executor (aot, interpreter, vm), for even the simplest program (id = fun x => x). They all happened to be caused by the exact same problem.

In Relay Implementation, a closure capture over all the free variables, holding a reference to all of them. However, it also hold a reference of itself (as long as it has a name, while ANF give everything a name). This make all closure cyclic data structure.
I propose to solve it by making the two following modification:
add a new Value, RecFunc. RecFunc hold a shared_ptr of a vector of closure, and a size_t index into it.
Every Closure will now have another field, a Map<Var, size_t> which store all recursive call (if any, including mutually recursive one) from the parent RecFunc.
This is in inspiration from https://cs.indiana.edu/ftp/techreports/TR73.pdf -
I will submit a PR soon, issuing here for visibility so ppl can avoid the same problem again.