diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,28 @@
 
 `hslua-core` uses [PVP Versioning][].
 
+## hslua-core-2.2.1
+
+Released 2022-06-19.
+
+-   Ensure that loadfile works with umlauts in filepath: The OS
+    does not necessarily expect filenames to be UTF-8 encoded,
+    especially Windows. On non-Windows systems, the current file
+    system encoding is now used to convert filenames to C
+    strings. On Windows, the `CP_ACP` codepage is used, as
+    required by the Windows API.
+
+-   GC managed Lua state: Add new type `GCManagedState` and
+    functions `newGCManagedState`, `closeGCManagedState`, and
+    `withGCManagedState`. These allow to create and use a Lua
+    state in flexible ways in that it does not require the state
+    to be closed explicitly. The state will be closed when the
+    respective variable is collected.
+
+-   Require lua-2.2.1.
+
+-   Relax upper bound for mtl, allow mtl-2.3.
+
 ## hslua-core-2.2.0
 
 Released 2022-02-19.
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright © 1994-2020 Lua.org, PUC-Rio.
+Copyright © 1994-2022 Lua.org, PUC-Rio.
 Copyright © 2007-2012 Gracjan Polak
 Copyright © 2012-2015 Ömer Sinan Ağacan
 Copyright © 2016-2022 Albert Krewinkel
diff --git a/hslua-core.cabal b/hslua-core.cabal
--- a/hslua-core.cabal
+++ b/hslua-core.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.2
 name:                hslua-core
-version:             2.2.0
+version:             2.2.1
 synopsis:            Bindings to Lua, an embeddable scripting language
 description:         Wrappers and helpers to bridge Haskell and
                      <https://www.lua.org/ Lua>.
@@ -27,8 +27,8 @@
                    , GHC == 8.6.5
                    , GHC == 8.8.4
                    , GHC == 8.10.7
-                   , GHC == 9.0.1
-                   , GHC == 9.2.1
+                   , GHC == 9.0.2
+                   , GHC == 9.2.3
 
 source-repository head
   type:                git
@@ -39,8 +39,8 @@
   build-depends:       base          >= 4.8    && < 5
                      , bytestring    >= 0.10.2 && < 0.12
                      , exceptions    >= 0.8    && < 0.11
-                     , lua           >= 2.2    && < 2.3
-                     , mtl           >= 2.2    && < 2.3
+                     , lua           >= 2.2.1  && < 2.3
+                     , mtl           >= 2.2    && < 2.4
                      , text          >= 1.2    && < 2.1
   ghc-options:         -Wall
                        -Wincomplete-record-updates
diff --git a/src/HsLua/Core/Auxiliary.hs b/src/HsLua/Core/Auxiliary.hs
--- a/src/HsLua/Core/Auxiliary.hs
+++ b/src/HsLua/Core/Auxiliary.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP                 #-}
 {-# LANGUAGE OverloadedStrings   #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications    #-}
@@ -48,11 +49,12 @@
 import Lua (top)
 import Lua.Auxiliary
 import Lua.Ersatz.Auxiliary
-import Foreign.C (withCString)
 import Foreign.Marshal.Alloc (alloca)
 import Foreign.Ptr
 
 import qualified Data.ByteString as B
+import qualified GHC.Foreign as GHC
+import qualified GHC.IO.Encoding as GHC
 import qualified HsLua.Core.Primary as Lua
 import qualified HsLua.Core.Types as Lua
 import qualified Foreign.Storable as Storable
@@ -174,8 +176,13 @@
 -- See <https://www.lua.org/manual/5.4/manual.html#luaL_loadfile luaL_loadfile>.
 loadfile :: FilePath -- ^ filename
          -> LuaE e Status
-loadfile fp = liftLua $ \l ->
-  withCString fp $! fmap Lua.toStatus . luaL_loadfile l
+loadfile fp = liftLua $ \l -> do
+#if defined(mingw32_HOST_OS)
+  fsEncoding <- GHC.mkTextEncoding "CP0"  -- a.k.a CP_ACP
+#else
+  fsEncoding <- GHC.getFileSystemEncoding
+#endif
+  GHC.withCString fsEncoding fp $! fmap Lua.toStatus . luaL_loadfile l
 {-# INLINABLE loadfile #-}
 
 -- | Loads a string as a Lua chunk. This function uses @lua_load@ to
diff --git a/src/HsLua/Core/Run.hs b/src/HsLua/Core/Run.hs
--- a/src/HsLua/Core/Run.hs
+++ b/src/HsLua/Core/Run.hs
@@ -14,10 +14,20 @@
   ( run
   , runEither
   , runWith
+    -- * GCManaged state
+  , GCManagedState
+  , newGCManagedState
+  , closeGCManagedState
+  , withGCManagedState
   ) where
 
 import Control.Exception (bracket, try)
+import Control.Monad ((<$!>))
+import Foreign.ForeignPtr
+  (ForeignPtr, finalizeForeignPtr, newForeignPtr, withForeignPtr)
 import HsLua.Core.Types (LuaE, runWith)
+import Lua.Primary (lua_close_ptr)
+import Lua (State (..))
 
 import qualified Control.Monad.Catch as Catch
 import qualified HsLua.Core.Auxiliary as Lua
@@ -37,3 +47,29 @@
 runEither :: Catch.Exception e => LuaE e a -> IO (Either e a)
 runEither = try . run
 {-# INLINABLE runEither #-}
+
+-- | Wrapper of a Lua state whose lifetime is managed by the Haskell
+-- garbage collector and has a finalizer attached. This means that the
+-- state does not have to be closed explicitly, but will be closed
+-- automatically when the value is garbage collected in Haskell.
+newtype GCManagedState = GCManagedState (ForeignPtr ())
+
+-- | Creates a new Lua state that is under the control of the Haskell
+-- garbage collector.
+newGCManagedState :: IO GCManagedState
+newGCManagedState = do
+  (State lptr) <- Lua.newstate
+  GCManagedState <$!> newForeignPtr lua_close_ptr lptr
+
+-- | Closes the Lua state and runs all finalizers associated with it.
+-- The state _may not_ be used after it has been closed.
+closeGCManagedState :: GCManagedState -> IO ()
+closeGCManagedState (GCManagedState fptr) = finalizeForeignPtr fptr
+
+-- | Runs a Lua action with a state that's managed by GC.
+withGCManagedState :: GCManagedState
+                   -> LuaE e a
+                   -> IO a
+withGCManagedState (GCManagedState fptr) action =
+  withForeignPtr fptr $ \lptr ->
+    runWith (State lptr) action
