emina / rosette

The Rosette solver-aided host language, sample solver-aided DSLs, and demos
Other
638 stars 74 forks source link

Unintentional value retain #247

Closed sorawee closed 1 year ago

sorawee commented 1 year ago

Consider the following program, modified from the example in the documentation for gc-terms!

; Creates n unreachable terms of the form (+ a (+ a ...)).
> (define (unused-terms! n)
   (define-symbolic* a integer?)
   (let loop ([n n])
     (if (<= n 1)
         a
         (+ a (loop (- n 1)))))
   (void))
> (time (unused-terms! 50000))
cpu time: 357 real time: 365 gc time: 17
> (gc-terms!)        ; Use gc-terms! to change the representation.
> (collect-garbage)
> (length (terms))
50000

Logically speaking, these 50000 terms should no longer be reachable, so (length (terms)) should ideally return 0. However, it returns 50000 because of the use of parameters (terms), which don't work well with GC. To quote the documentation of parameters (https://docs.racket-lang.org/reference/parameters.html):

as far as the memory manager is concerned, the value originally associated with a parameter through parameterize remains reachable as long the continuation is reachable, even if the parameter is mutated.

In the above example, terms (actually current-terms) is initially a parameter that holds a hash retaining its keys strongly. Even when we assign terms to a hash retaining its keys weakly, the original hash is still unfortunately retained.

One possible fix is to clear the original hash explicitly. Another possibility is to initialize/parameterize current-terms to #f and immediately mutate it to the desired value afterward.