effectful-core 2.7.1.0 → 2.7.1.1
raw patch · 3 files changed
+28/−5 lines, 3 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG.md +4/−0
- effectful-core.cabal +5/−1
- src/Effectful/Dispatch/Dynamic.hs +19/−4
CHANGELOG.md view
@@ -1,3 +1,7 @@+# effectful-core-2.7.1.1 (2026-08-24)+* Fix a performance regression introduced in 2.7.0.0 that increased the+ per-operation overhead of dynamically dispatched effects.+ # effectful-core-2.7.1.0 (2026-08-24) * Export `seqForkUnliftIO` and add `unsafeSeqForkUnliftIO` in `Effectful.Dispatch.Static` for the `SeqForkUnlift` strategy.
effectful-core.cabal view
@@ -1,7 +1,7 @@ cabal-version: 3.8 build-type: Simple name: effectful-core-version: 2.7.1.0+version: 2.7.1.1 license: BSD-3-Clause license-file: LICENSE category: Control@@ -51,6 +51,10 @@ library import: language + -- -O2 enables -ffast-pap-calls, without which the handler invocation in+ -- send and passthrough (an unknown call with 5 pointers and the State#+ -- token that matches no precompiled RTS apply pattern) allocates an+ -- intermediate PAP on every operation of a dynamically dispatched effect. ghc-options: -O2 build-depends: base >= 4.18 && < 5
src/Effectful/Dispatch/Dynamic.hs view
@@ -1210,6 +1210,21 @@ ---------------------------------------- -- Helpers +-- Note [NOINLINE on the Impl functions]+--+-- The public wrappers must inline at call sites, because that's the only place+-- the callstack-thawing adapter around the handler (see 'HandlerImpl') can+-- fuse with a concrete handler. If it doesn't, the adapter is stored in the+-- 'Handler' as a closure over an unknown function and every 'send' pays for an+-- extra unknown call, CallStack allocation and generic apply of a PAP.+--+-- The wrappers have no INLINE pragmas, so they expose their optimized RHS as+-- the unfolding and GHC inlines them based on its size. NOINLINE below keeps+-- the bodies of the Impl functions out of these unfoldings, guaranteeing that+-- they stay small. Historically reinterpretImpl had an INLINE pragma instead+-- and a minor addition to it grew the wrappers of reinterpret past the+-- inlining threshold, silently causing exactly this regression.+ interpretImpl :: (HasCallStack, DispatchOf e ~ Dynamic) => Eff (e : es) a@@ -1217,7 +1232,7 @@ -> Eff es a interpretImpl action handlerImpl = unsafeEff $ \es -> do (`unEff` es) $ runHandler (Handler es handlerImpl) action-{-# INLINE interpretImpl #-}+{-# NOINLINE interpretImpl #-} reinterpretImpl :: (HasCallStack, DispatchOf e ~ Dynamic)@@ -1229,7 +1244,7 @@ (`unEff` es) . runSetup . unsafeEff $ \handlerEs -> do requireInScopeSetup es handlerEs (`unEff` es) $ runHandler (Handler handlerEs handlerImpl) action-{-# INLINE reinterpretImpl #-}+{-# NOINLINE reinterpretImpl #-} interposeImpl :: forall e es a. (HasCallStack, DispatchOf e ~ Dynamic, e :> es)@@ -1253,7 +1268,7 @@ putEnv es $ Handler newEs handlerImpl unEff action es )-{-# INLINE interposeImpl #-}+{-# NOINLINE interposeImpl #-} imposeImpl :: forall e es handlerEs a b. (HasCallStack, DispatchOf e ~ Dynamic, e :> es)@@ -1281,7 +1296,7 @@ putEnv es $ Handler handlerEs handlerImpl unEff action es )-{-# INLINE imposeImpl #-}+{-# NOINLINE imposeImpl #-} copyRefs :: forall es srcEs destEs