Skip to content

winio: xchg semantics that got merged not the same as winio expected

when !3293 (closed) was split off from the main winio MR the semantics were slightly changed and I hadn't caught it at the time.

Essentially the semantics were changed to do what the xchg operation semantically does but not what winio operationally needed, that is xchg swaps the values in both src and dst.

For winio this is problematic as the instruction is used as

oldDataPtr <- exchangePtr ptr_lpol nullReq

where nullReq is a CAF created by

-- | Terminator symbol for IOCP request
nullReq :: Ptr (Ptr a)
nullReq = castPtr $ unsafePerformIO $ new $ (nullPtr :: Ptr ())

the intention is to be able to test oldDataPtr after the swap to see if it's nullReq, if it is then something else already handled the request. This is why the original primop was a RMM instead of RM and forced a copy.

however now that nullReq also changes this check will never be true. This shows itself as a race condition, if the request is completed synchronously you never see it, if one of them is completed asynchronously then you will deference an invalid pointer and segfault.

Now given that the semantics of interlockedExchangeAddr# matches the expected semantics of the instruction now I think it should probably stay like that, but I am unsure how to fix this issue.

I don't know Cmm well enough, but can I enforce a copy of the value in

{-# INLINE exchangePtr #-}
exchangePtr :: Ptr (Ptr a) -> Ptr b -> IO (Ptr c)
exchangePtr (Ptr dst) (Ptr val) =
  IO $ \s ->
      case (interlockedExchangeAddr# dst val s) of
        (# s2, old_val #) -> (# s2, Ptr old_val #)

? As in, is there something I can use that always generates a copy (with no additional overhead)?

Edited by Tamar Christina
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information