Missing nonmoving write barriers in selector optimisation
The moving GC's selector optimisation implementation is currently missing a few subtle update remembered set write barriers:
-
https://gitlab.haskell.org/ghc/ghc/-/blob/929161943f19e1673288adc83d165ddc99865798/rts/sm/Evac.c#L1255: when we encounter a closure in the nonmoving generation (or living in moving to-space) we abort the optimisation. However, also we do not
evacuate
the closure (which is understandable given this code path originally just handled the to-space case, in which case the closure has been by definition already evacuated). This means that we failed to mark such terminal closures during the preparatory GC. -
https://gitlab.haskell.org/ghc/ghc/-/blob/929161943f19e1673288adc83d165ddc99865798/rts/sm/Evac.c#L1426: If we encounter a constructor we must take care to mark the result if it is in the non-moving heap, even if the
evac
flag is not set (as it will be whenevac_thunk_selector
recurses) -
https://gitlab.haskell.org/ghc/ghc/-/blob/929161943f19e1673288adc83d165ddc99865798/rts/sm/Evac.c#L1516: When we
bail_out
of the traversal loop, we callcopy
rather than evacuate on the final result. Consequently, we fail to mark the final result.
Likewise, the non-moving GC's implementation of the selector optimisation may break the snapshot invariant:
-
https://gitlab.haskell.org/ghc/ghc/-/blob/929161943f19e1673288adc83d165ddc99865798/rts/sm/NonMovingMark.c#L659: when we encounter a
THUNK_SELECTOR
inupdateRemembSetPushThunkEager
we must treat it like any other thunk and eagerly push its free variables (namely, the selectee) - https://gitlab.haskell.org/ghc/ghc/-/blob/929161943f19e1673288adc83d165ddc99865798/rts/sm/NonMovingMark.c#L1584: When we perform the selector optimisation we must mark the selectee to preserve the snapshot invariant