This is a very good question.
The canonical way of doing something like this is to steal bits from the pointer, and then use 64 bit CAS. (32 bit systems need different considerations, I’m only thinking about 64 bit now)
There are two issues with that:
-
That requires runtime support: The garbage collector needs to be taught about that for the mark phase. There was a project to bring GC plugins for custom types, but afaiu that never got in.
-
That may require codegen support, depending on your performance requirements: Basically a better variant of unsafe_pointer_to_objref
(i.e. one that can be taught to trust the user-provided type and that can be annotated with some info about GC-rooting).
What I would consider doing is something like the following:
mutable struct Container_for_cmpxchg16b{T} #T must be reference-type
flag::UInt64
actual_value::T
shadow_value::Any
end
Then you use the cmpxchg16b
instruction to simultaneously CAS flag
and actual_value
, via pointer-slinging / pointer_from_objref(container)
. Cf Threads.atomic_cas!
for the code you need in the llvmcall
.
There is a second issue: There is a write-barrier for generational garbage collection. You can see the write barrier by inspecting code for
julia> mutable struct C{T}
x::T
end
julia> g(c, x) = begin c.x=x; 1 end
g (generic function with 3 methods)
julia> @code_native g(C("a"), "b")
...
What this boils down to is: If your container is old-generation and you use CAS to plug in a young-generation value, then some GC rooting is required. I’m sure there are better ways using the C-api, but my boring naive approach would be to follow a successful CAS-write up with a non-atomic write to shadow_value
.
Now shadow_value
and actual_value
may diverge, due to lack of atomicity. The worst-case effect of that should be that the object pointed at by shadow_value
has artificially extended lifespan, i.e. a temporary memory leak.
Sorry that this is so ugly
Maybe somebody else has better ideas?
PS. You can of course do as java7 claims to have been doing, i.e.
Implementation note: This implementation maintains markable references by creating internal objects representing “boxed” [reference, boolean] pairs.
I haven’t looked into current jvm implementations. Eating an entire additional allocation + memory indirection seems very expensive to me, and completely defies the point of the data type. (but yes, without dedicated runtime/compiler support, this is what they must do)
PPS. Yep, java still does it the boxed way. You can relatively directly port jdk/src/java.base/share/classes/java/util/concurrent/atomic/AtomicMarkableReference.java at 64bbae75121ccf80c02a0960e2db62eb558052e6 · openjdk/jdk · GitHub to julia.