On Thu, Sep 17, 2015 at 6:48 AM, Phil Clayton <phil.clayton at lineone.net> wrote:
16/09/15 12:40, David Matthews wrote:
On 15/09/2015 22:18, Phil Clayton wrote:
I was wondering how to implement the 'touch' function of MLTON_FINALIZABLE that forces a weak reference to stay alive. The expression ignore (PolyML.pointerEq (x, x) orelse raise Fail "touch"; print ""); seems to prevent Poly/ML optimizing the dependence on x away and works for any type x. Bizarrely, I found that without print "", the weak reference stayed alive. Can you think of something simpler?
I looked at the MLton documentation and couldn't understand what "touch" was trying to achieve. Could you explain it?
"touch t" prevents finalization of t until the call has been evaluated. "touch t" an operation that uses t to do nothing, so t cannot be garbage-collected until the operation has finished.
Essentially, yes.
"MLton.Finalizable.touch v" is equivalent to "MLton.Finalizable.withValue (v, fn _ => ())", because "withValue" guarantees that the finalizers of v will not run before the function completes. Of course, internally, both "MLton.Finalizable.touch" and "MLton.Finalizable.withValue" are implemented using a more primitive "touch" with the more general type 'a -> unit. This primitive is treated by the compiler as an unoptimizable reference to the object; essentially, it is a future proof method of maintaining a strong reference to an object and its main use case is to maintain that strong reference to an object for which there is a weak reference.
For example, suppose one had:
val token = ref () val wtoken = Weak.new token ... val v1 = Weak.get wtoken ... !token (* and no subsequent uses of token *) ... val v2 = Weak.get wtoken ...
One might expect that v1 is SOME and v2 is NONE. But, MLton's optimizations are somewhat tuned towards SML without extensions such as weak references, so it would be reasonable to replace the "!token" with "()". And then v1 could be NONE. And replacing "!token" with "token := !token" might be optimized away in a future version of the compiler. The "touch" primitive, which perhaps should also be exported somewhere else in "structure MLton", provides a robust method instead of the fragile method of crafting a sophisticated nop that isn't optimized away. But, thus far, the main use case has been for finalizers.
Is the issue that a global optimising compiler such as MLton could work out that the token was not referenced even though the item was and remove it from the pair at the last reference?
I don't think that is the issue here (as we don't have an item).
In the context of MLton (and now in Poly/ML), the token is a Weak reference to the item and the check for when to run the finalizers is whether or not the weak reference has gone NONE. So, it is something like the example above that could cause a finalizer to run earlier than expected if there weren't a touch in each use of the finalized value.
Poly/ML only does that in very limited circumstances. Is the idea of "touch" that this counts as a reference to the token and that you add a call to it after each reference to the item so that the lifetime of the token is no less than the lifetime of the item?
Yes, but as there is no item, the purpose of touch is to delay finalization until some other event has occurred. One example use of touch is in the implementation of Finalizable.finalizeBefore. The 'other event' is finalization of another finalizable value.
Right. The use case for things like "Finalizable.finalizeBefore" is when one has a number of C pointers (which to SML look like simple Word64.word-s), where one knows that it is important to free the pointed-to C data structures in the right order. MLton can be a little more aggressive about "Word64.word ref"s (like flattening them into a containing data structure), so the "Word64.word ref" and the Weak reference to it can get a little out of sync as above. Again, a "touch" in the right place ensures that the reference is flattened and the Weak reference properly tracks the object.
-Matthew