Consider Ref_alloc and Ref_uninit primitives
It would be possible to extend the ideas from #207 to references. For example, we could have:
structure Unsafe.Ref:
sig
val alloc: unit -> 'a ref
val uninit: 'a ref -> unit
structure Raw:
sig
type 'a rawref
val alloc: unit -> 'a rawref
val toRef: 'a rawref -> 'a ref
val uninit: 'a rawref -> unit
end
end
One potential motivation is to eliminate the (admittedly small) dispatch overhead in a ('a -> 'b) ref that is only ever meant to hold one function. For example, recall the classic "back-patching" technique for recursion with first-class functions and references:
val fact_ref = ref (fn _ => raise Fail "fact")
val fact = fn n => if n <= 1 then 1 else n * (!fact_ref) (n - 1)
val () = fact_ref := fact
Control-flow analysis will determine that the contents of fact_ref is either fn _ => raise Fail "fact" or fn n => ..., and the application (!fact_ref) (n - 1) will have a case-analysis to select between these functions. [Note that the overhead is essentially the same, but more obvious to the programmer, when using an ('a -> 'b) option ref.]
With a Ref_alloc primitive, one could write:
val fact_ref = Unsafe.Ref.alloc ()
val fact = fn n => if n <= 1 then 1 else n * (!fact_ref) (n - 1)
val () = fact_ref := fact
Control-flow analysis will determine that the contents of fact_ref is only fn n => ... and the application will have a single (exhaustive) case-match. Subsequent ConstantPropagation and/or Useless optimizations would probably eliminate the reference entirely.
Another potential use of Ref_alloc and Ref_uninit would be to support Thread_copyCurrent, which currently "returns" the copied thread via GC_getSavedThread, but could be changed to take a thread ref and write the copied thread there. Using Ref_alloc to allocate the thread ref would be convenient, because it is otherwise not easy to obtain a thread to initialize the ref (only to be overwritten).