diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,32 @@
 
 `hslua-core` uses [PVP Versioning][].
 
+## hslua-core-2.2.0
+
+Released 2022-02-19.
+
+-    Use lua-2.2.0, which requires Lua 5.4.
+
+-    Rename `newuserdata` to `newuserdatauv` and let it take the
+     number of associated uservalues as an additional argument.
+
+     Similarly, `newhsuserdata` is now `newhsuserdatauv`.
+
+-    Rename `getuservalue` and `setuservalue` to `getiuservalue`
+     and `setiuservalue`, respectively. Like both functions now
+     take an extra argument specifying the number of the uservalue
+     that should be retrieved or set.
+
+     It is now possible for `setiuservalue` to fail, so it returns
+     a boolean to indicate whether the action was successful.
+
+-    The `GCControl` type has been updated to match the new gc
+     control:
+
+     -   The GCStep constructor takes an argument "stepsize";
+     -   constructors GCGen and GCInc have been added;
+     -   constructors GCSetPause and GCSetStepMul have been removed.
+
 ## hslua-core-2.1.0
 
 Released 29-01-2022.
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.1.0
+version:             2.2.0
 synopsis:            Bindings to Lua, an embeddable scripting language
 description:         Wrappers and helpers to bridge Haskell and
                      <https://www.lua.org/ Lua>.
@@ -39,7 +39,7 @@
   build-depends:       base          >= 4.8    && < 5
                      , bytestring    >= 0.10.2 && < 0.12
                      , exceptions    >= 0.8    && < 0.11
-                     , lua           >= 2.1    && < 2.2
+                     , lua           >= 2.2    && < 2.3
                      , mtl           >= 2.2    && < 2.3
                      , text          >= 1.2    && < 2.1
   ghc-options:         -Wall
diff --git a/src/HsLua/Core.hs b/src/HsLua/Core.hs
--- a/src/HsLua/Core.hs
+++ b/src/HsLua/Core.hs
@@ -114,9 +114,9 @@
   , rawgeti
   , createtable
   , newtable
-  , newuserdata
+  , newuserdatauv
   , getmetatable
-  , getuservalue
+  , getiuservalue
   -- ** set functions (stack → Lua)
   , setglobal
   , settable
@@ -124,7 +124,7 @@
   , rawset
   , rawseti
   , setmetatable
-  , setuservalue
+  , setiuservalue
   -- ** load and call functions (load and run Lua code)
   , call
   , pcall
@@ -186,7 +186,7 @@
   -- * Haskell userdata values
   --
   -- | Push arbitrary Haskell values to the Lua stack.
-  , newhsuserdata
+  , newhsuserdatauv
   , newudmetatable
   , fromuserdata
   , putuserdata
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
@@ -171,7 +171,7 @@
 -- As @'Lua.load'@, this function only loads the chunk; it does not run
 -- it.
 --
--- See <https://www.lua.org/manual/5.3/manual.html#luaL_loadfile luaL_loadfile>.
+-- See <https://www.lua.org/manual/5.4/manual.html#luaL_loadfile luaL_loadfile>.
 loadfile :: FilePath -- ^ filename
          -> LuaE e Status
 loadfile fp = liftLua $ \l ->
@@ -189,7 +189,7 @@
 -- run it.
 --
 -- See
--- <https://www.lua.org/manual/5.3/manual.html#luaL_loadstring luaL_loadstring>.
+-- <https://www.lua.org/manual/5.4/manual.html#luaL_loadstring luaL_loadstring>.
 loadstring :: ByteString -> LuaE e Status
 loadstring s = loadbuffer s (Name s)
 {-# INLINE loadstring #-}
@@ -214,12 +214,12 @@
 
 -- | Creates a new Lua state. It calls @lua_newstate@ with an allocator
 -- based on the standard C @realloc@ function and then sets a panic
--- function (see <https://www.lua.org/manual/5.3/manual.html#4.6 §4.6>
--- of the Lua 5.3 Reference Manual) that prints an error message to the
+-- function (see <https://www.lua.org/manual/5.4/manual.html#4.4 §4.4>
+-- of the Lua 5.4 Reference Manual) that prints an error message to the
 -- standard error output in case of fatal errors.
 --
 -- Wraps 'hsluaL_newstate'. See also:
--- <https://www.lua.org/manual/5.3/manual.html#luaL_newstate luaL_newstate>.
+-- <https://www.lua.org/manual/5.4/manual.html#luaL_newstate luaL_newstate>.
 newstate :: IO Lua.State
 newstate = hsluaL_newstate
 {-# INLINE newstate #-}
@@ -246,7 +246,7 @@
 -- function @openf@ with string @modname@ as an argument and sets the
 -- call result in @package.loaded[modname]@, as if that function has
 -- been called through
--- <https://www.lua.org/manual/5.3/manual.html#pdf-require require>.
+-- <https://www.lua.org/manual/5.4/manual.html#pdf-require require>.
 --
 -- If @glb@ is true, also stores the module into global @modname@.
 --
@@ -303,7 +303,7 @@
 -- used again.
 --
 -- Wraps 'luaL_unref'. See also:
--- <https://www.lua.org/manual/5.3/manual.html#luaL_unref luaL_unref>.
+-- <https://www.lua.org/manual/5.4/manual.html#luaL_unref luaL_unref>.
 unref :: StackIndex -- ^ idx
       -> Reference  -- ^ ref
       -> LuaE e ()
diff --git a/src/HsLua/Core/Package.hs b/src/HsLua/Core/Package.hs
--- a/src/HsLua/Core/Package.hs
+++ b/src/HsLua/Core/Package.hs
@@ -29,7 +29,7 @@
 -- @package.loaded@, calls function @openf@ with string @modname@ as an
 -- argument and sets the call result in @package.loaded[modname]@, as if
 -- that function has been called through
--- <https://www.lua.org/manual/5.3/manual.html#pdf-require require>.
+-- <https://www.lua.org/manual/5.4/manual.html#pdf-require require>.
 --
 -- Leaves a copy of the module on the stack.
 requirehs :: LuaError e
diff --git a/src/HsLua/Core/Primary.hs b/src/HsLua/Core/Primary.hs
--- a/src/HsLua/Core/Primary.hs
+++ b/src/HsLua/Core/Primary.hs
@@ -98,7 +98,7 @@
 -- to its original configuration. This is considered good programming
 -- practice.
 --
--- See <https://www.lua.org/manual/5.3/manual.html#lua_call lua_call>.
+-- See <https://www.lua.org/manual/5.4/manual.html#lua_call lua_call>.
 call :: LuaError e => NumArgs -> NumResults -> LuaE e ()
 call nargs nresults = do
   res <- pcall nargs nresults Nothing
@@ -145,7 +145,7 @@
 --    LE: compares for less or equal (<=)
 --
 -- Wraps 'hslua_compare'. See also
--- <https://www.lua.org/manual/5.3/manual.html#lua_compare lua_compare>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_compare lua_compare>.
 compare :: LuaError e
         => StackIndex         -- ^ idx1
         -> StackIndex         -- ^ idx2
@@ -160,11 +160,11 @@
 -- value on the stack (that is, the function does nothing); if @n@ is 0,
 -- the result is the empty string. Concatenation is performed following
 -- the usual semantics of Lua (see
--- <https://www.lua.org/manual/5.3/manual.html#3.4.6 §3.4.6> of the Lua
+-- <https://www.lua.org/manual/5.4/manual.html#3.4.6 §3.4.6> of the Lua
 -- manual).
 --
 -- Wraps 'hslua_concat'. See also
--- <https://www.lua.org/manual/5.3/manual.html#lua_concat lua_concat>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_concat lua_concat>.
 concat :: LuaError e => NumArgs -> LuaE e ()
 concat n = liftLuaThrow (`hslua_concat` n)
 {-# INLINABLE concat #-}
@@ -219,20 +219,21 @@
 --
 -- Wraps 'lua_gc'.
 gc :: GCControl -> LuaE e Int
-gc what = liftLua $ \l ->
-  fromIntegral <$!> lua_gc l (toGCcode what) (toGCdata what)
+gc what = liftLua $ \l -> do
+  let (data1, data2, data3) = toGCdata what
+  fromIntegral <$!> lua_gc l (toGCcode what) data1 data2 data3
 {-# INLINABLE gc #-}
 
 -- | Pushes onto the stack the value @t[k]@, where @t@ is the value at
 -- the given stack index. As in Lua, this function may trigger a
 -- metamethod for the "index" event (see
--- <https://www.lua.org/manual/5.3/manual.html#2.4 §2.4> of Lua's
+-- <https://www.lua.org/manual/5.4/manual.html#2.4 §2.4> of Lua's
 -- manual).
 --
 -- Errors on the Lua side are propagated.
 --
 -- See also
--- <https://www.lua.org/manual/5.3/manual.html#lua_getfield lua_getfield>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_getfield lua_getfield>.
 getfield :: LuaError e => StackIndex -> Name -> LuaE e Type
 getfield i (Name s) = do
   absidx <- absindex i
@@ -267,13 +268,13 @@
 -- This function pops the key from the stack, pushing the resulting
 -- value in its place. As in Lua, this function may trigger a metamethod
 -- for the "index" event (see
--- <https://www.lua.org/manual/5.3/manual.html#2.4 §2.4> of Lua's
+-- <https://www.lua.org/manual/5.4/manual.html#2.4 §2.4> of Lua's
 -- manual).
 --
 -- Errors on the Lua side are caught and rethrown.
 --
 -- Wraps 'hslua_gettable'. See also:
--- <https://www.lua.org/manual/5.3/manual.html#lua_gettable lua_gettable>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_gettable lua_gettable>.
 gettable :: LuaError e => StackIndex -> LuaE e Type
 gettable n = liftLuaThrow (\l -> fmap toType . hslua_gettable l n)
 {-# INLINABLE gettable #-}
@@ -287,14 +288,19 @@
 gettop = liftLua lua_gettop
 {-# INLINABLE gettop #-}
 
--- | Pushes onto the stack the Lua value associated with the full
--- userdata at the given index.
+-- | Pushes onto the stack the @n@-th user value associated with the
+-- full userdata at the given index and returns the type of the pushed
+-- value.
 --
--- Returns the type of the pushed value.
+-- If the userdata does not have that value, pushes __nil__ and returns
+-- 'LUA_TNONE'.
 --
--- Wraps 'lua_getuservalue'.
-getuservalue :: StackIndex -> LuaE e Type
-getuservalue idx = toType <$> liftLua (`lua_getuservalue` idx)
+-- Wraps 'lua_getiuservalue'.
+getiuservalue :: StackIndex  -- ^ index
+             -> Int         -- ^ n
+             -> LuaE e Type
+getiuservalue idx n = liftLua $ \l ->
+  toType <$!> lua_getiuservalue l idx (fromIntegral n)
 
 -- | Moves the top element into the given valid index, shifting up the
 -- elements above this index to open space. This function cannot be
@@ -442,7 +448,7 @@
 --
 -- The @chunkname@ argument gives a name to the chunk, which is used for
 -- error messages and in debug information (see
--- <https://www.lua.org/manual/5.3/manual.html#4.9 §4.9>). Note that the
+-- <https://www.lua.org/manual/5.4/manual.html#4.7 §4.7>). Note that the
 -- @chunkname@ is used as a C string, so it may not contain null-bytes.
 --
 -- This is a wrapper of 'lua_load'.
@@ -464,19 +470,29 @@
 -- equivalent to @createtable 0 0@.
 --
 -- See also:
--- <https://www.lua.org/manual/5.3/manual.html#lua_newtable lua_newtable>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_newtable lua_newtable>.
 newtable :: LuaE e ()
 newtable = createtable 0 0
 {-# INLINABLE newtable #-}
 
--- | This function allocates a new block of memory with the given size,
--- pushes onto the stack a new full userdata with the block address, and
--- returns this address. The host program can freely use this memory.
+-- | This function creates and pushes on the stack a new full userdata,
+-- with @nuvalue@ associated Lua values, called @user values@, plus an
+-- associated block of raw memory with @size@ bytes. (The user values
+-- can be set and read with the functions 'lua_setiuservalue' and
+-- 'lua_getiuservalue'.)
 --
--- This function wraps 'lua_newuserdata'.
-newuserdata :: Int -> LuaE e (Ptr ())
-newuserdata = liftLua1 lua_newuserdata . fromIntegral
-{-# INLINABLE newuserdata #-}
+-- The function returns the address of the block of memory. Lua ensures
+-- that this address is valid as long as the corresponding userdata is
+-- alive (see <https://www.lua.org/manual/5.4/manual.html#2.5 §2.5>).
+-- Moreover, if the userdata is marked for finalization (see
+-- <https://www.lua.org/manual/5.4/manual.html#2.5.3 §2.5.3>), its
+-- address is valid at least until the call to its finalizer.
+--
+-- This function wraps 'lua_newuserdatauv'.
+newuserdatauv :: Int {- ^ size -} -> Int {- ^ nuvalue -} -> LuaE e (Ptr ())
+newuserdatauv size nuvalue =
+  liftLua $ \l -> lua_newuserdatauv l (fromIntegral size) (fromIntegral nuvalue)
+{-# INLINABLE newuserdatauv #-}
 
 -- | Pops a key from the stack, and pushes a key–value pair from the
 -- table at the given index (the "next" pair after the given key). If
@@ -487,7 +503,7 @@
 --
 -- This function wraps 'hslua_next'.
 -- See also:
--- <https://www.lua.org/manual/5.3/manual.html#lua_next lua_next>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_next lua_next>.
 next :: LuaError e => StackIndex -> LuaE e Bool
 next idx = fromLuaBool <$!> liftLuaThrow (\l -> hslua_next l idx)
 {-# INLINABLE next #-}
@@ -585,7 +601,7 @@
 
 -- | Pops @n@ elements from the stack.
 --
--- See also: <https://www.lua.org/manual/5.3/manual.html#lua_pop lua_pop>.
+-- See also: <https://www.lua.org/manual/5.4/manual.html#lua_pop lua_pop>.
 pop :: Int -> LuaE e ()
 pop n = liftLua $ \l -> lua_pop l (fromIntegral n)
 {-# INLINABLE pop #-}
@@ -625,7 +641,7 @@
 -- to receive its parameters and return its results (see @'CFunction'@)
 --
 -- Same as @flip 'pushcclosure' 0@.
--- <https://www.lua.org/manual/5.3/manual.html#lua_pushcfunction lua_pushcfunction>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_pushcfunction lua_pushcfunction>.
 pushcfunction :: CFunction -> LuaE e ()
 pushcfunction f = pushcclosure f 0
 {-# INLINABLE pushcfunction #-}
@@ -790,7 +806,7 @@
 -- This function cannot be called with a pseudo-index, because a
 -- pseudo-index is not an actual stack position.
 --
--- <https://www.lua.org/manual/5.3/manual.html#lua_rotate>
+-- <https://www.lua.org/manual/5.4/manual.html#lua_rotate>
 rotate :: StackIndex  -- ^ @idx@
        -> Int         -- ^ @n@
        -> LuaE e ()
@@ -802,13 +818,13 @@
 --
 -- This function pops the value from the stack. As in Lua, this function
 -- may trigger a metamethod for the "newindex" event (see
--- <https://www.lua.org/manual/5.3/manual.html#2.4 §2.4> of the Lua 5.3
+-- <https://www.lua.org/manual/5.4/manual.html#2.4 §2.4> of the Lua 5.4
 -- Reference Manual).
 --
 -- Errors on the Lua side are caught and rethrown as a @'Exception'@.
 --
 -- See also:
--- <https://www.lua.org/manual/5.3/manual.html#lua_setfield lua_setfield>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_setfield lua_setfield>.
 setfield :: LuaError e => StackIndex -> Name -> LuaE e ()
 setfield i (Name s) = do
   absidx <- absindex i
@@ -823,7 +839,7 @@
 -- Errors on the Lua side are caught and rethrown as 'Exception'.
 --
 -- Wraps 'hslua_setglobal'. See also:
--- <https://www.lua.org/manual/5.3/manual.html#lua_setglobal lua_setglobal>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_setglobal lua_setglobal>.
 setglobal :: LuaError e => Name {- ^ name -} -> LuaE e ()
 setglobal (Name name) = liftLuaThrow $ \l status' ->
   B.unsafeUseAsCStringLen name $ \(namePtr, nameLen) ->
@@ -844,8 +860,8 @@
 --
 -- This function pops both the key and the value from the stack. As in
 -- Lua, this function may trigger a metamethod for the "newindex" event
--- (see <https://www.lua.org/manual/5.3/manual.html#2.4 §2.4> of the Lua
--- 5.3 Reference Manual).
+-- (see <https://www.lua.org/manual/5.4/manual.html#2.4 §2.4> of the Lua
+-- 5.4 Reference Manual).
 --
 -- Errors on the Lua side are caught and rethrown.
 --
@@ -863,12 +879,14 @@
 settop = liftLua1 lua_settop
 {-# INLINABLE settop #-}
 
--- | Pops a value from the stack and sets it as the new value associated
--- to the full userdata at the given index.
+-- | Pops a value from the stack and sets it as the new @n@-th user
+-- value associated to the full userdata at the given index. Returns 0
+-- if the userdata does not have that value.
 --
--- <https://www.lua.org/manual/5.3/manual.html#lua_setuservalue>
-setuservalue :: StackIndex -> LuaE e ()
-setuservalue idx = liftLua (`lua_setuservalue` idx)
+-- Wraps 'lua_setiuservalue'.
+setiuservalue :: StackIndex {- ^ index -} -> Int {- ^ n -} -> LuaE e Bool
+setiuservalue idx n = liftLua $ \l ->
+  fromLuaBool <$!> lua_setiuservalue l idx (fromIntegral n)
 
 -- |  Returns the status of this Lua thread.
 --
@@ -909,14 +927,14 @@
 -- | Converts the Lua value at the given acceptable index to the signed
 -- integral type 'Lua.Integer'. The Lua value must be an integer, a
 -- number or a string convertible to an integer (see
--- <https://www.lua.org/manual/5.3/manual.html#3.4.3 §3.4.3> of the Lua
--- 5.3 Reference Manual); otherwise, @tointeger@ returns @Nothing@.
+-- <https://www.lua.org/manual/5.4/manual.html#3.4.3 §3.4.3> of the Lua
+-- 5.4 Reference Manual); otherwise, @tointeger@ returns @Nothing@.
 --
 -- If the number is not an integer, it is truncated in some
 -- non-specified way.
 --
 -- Wraps 'lua_tointegerx'. See also:
--- <https://www.lua.org/manual/5.3/manual.html#lua_tointeger lua_tointeger>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_tointeger lua_tointeger>.
 tointeger :: StackIndex -> LuaE e (Maybe Lua.Integer)
 tointeger n = liftLua $ \l -> alloca $ \boolPtr -> do
   res <- lua_tointegerx l n boolPtr
@@ -929,7 +947,7 @@
 -- otherwise, @tonumber@ returns @'Nothing'@.
 --
 -- Wraps 'lua_tonumberx'. See also
--- <https://www.lua.org/manual/5.3/manual.html#lua_tonumber lua_tonumber>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_tonumber lua_tonumber>.
 tonumber :: StackIndex -> LuaE e (Maybe Lua.Number)
 tonumber n = liftLua $ \l -> alloca $ \bptr -> do
   res <- lua_tonumberx l n bptr
@@ -1006,11 +1024,11 @@
 {-# INLINABLE typename #-}
 
 -- | Returns the pseudo-index that represents the @i@-th upvalue of the
--- running function (see <https://www.lua.org/manual/5.3/manual.html#4.4
--- §4.4> of the Lua 5.3 reference manual).
+-- running function (see <https://www.lua.org/manual/5.4/manual.html#4.2
+-- §4.2> of the Lua 5.4 reference manual).
 --
 -- See also:
--- <https://www.lua.org/manual/5.3/manual.html#lua_upvalueindex lua_upvalueindex>.
+-- <https://www.lua.org/manual/5.4/manual.html#lua_upvalueindex lua_upvalueindex>.
 upvalueindex :: StackIndex -> StackIndex
 upvalueindex i = registryindex - i
 {-# INLINABLE upvalueindex #-}
diff --git a/src/HsLua/Core/Types.hs b/src/HsLua/Core/Types.hs
--- a/src/HsLua/Core/Types.hs
+++ b/src/HsLua/Core/Types.hs
@@ -141,7 +141,7 @@
 --
 
 -- | Enumeration used as type tag.
--- See <https://www.lua.org/manual/5.3/manual.html#lua_type lua_type>.
+-- See <https://www.lua.org/manual/5.4/manual.html#lua_type lua_type>.
 data Type
   = TypeNone           -- ^ non-valid stack index
   | TypeNil            -- ^ type of Lua's @nil@ value
@@ -204,7 +204,6 @@
   | ErrSyntax -- ^ syntax error during precompilation
   | ErrMem    -- ^ memory allocation (out-of-memory) error.
   | ErrErr    -- ^ error while running the message handler.
-  | ErrGcmm   -- ^ error while running a @__gc@ metamethod.
   | ErrFile   -- ^ opening or reading a file failed.
   deriving (Eq, Show)
 
@@ -216,7 +215,6 @@
   LUA_ERRRUN    -> ErrRun
   LUA_ERRSYNTAX -> ErrSyntax
   LUA_ERRMEM    -> ErrMem
-  LUA_ERRGCMM   -> ErrGcmm
   LUA_ERRERR    -> ErrErr
   LUA_ERRFILE   -> ErrFile
   StatusCode n  -> error $ "Cannot convert (" ++ show n ++ ") to Status"
@@ -263,25 +261,27 @@
 
 -- | Commands to control the garbage collector.
 data GCControl
-  = GCStop            -- ^ stops the garbage collector.
-  | GCRestart         -- ^ restarts the garbage collector
-  | GCCollect         -- ^ performs a full garbage-collection cycle.
-  | GCCount           -- ^ returns the current amount of memory (in
-                      -- Kbytes) in use by Lua.
-  | GCCountb          -- ^ returns the remainder of dividing the current
-                      -- amount of bytes of memory in use by Lua by 1024.
-  | GCStep            -- ^ performs an incremental step of garbage
-                      -- collection.
-  | GCSetPause CInt   -- ^ sets data as the new value for the pause of
-                      -- the collector (see
-                      -- <https://www.lua.org/manual/5.3/manual.html#2.5
-                      -- §2.5> of the Lua reference manual) and returns
-                      -- the previous value of the pause.
-  | GCSetStepMul CInt -- ^ sets data as the new value for the step
-                      -- multiplier of the collector (see
-                      -- <https://www.lua.org/manual/5.3/manual.html#2.5
-                      -- §2.5> of the Lua reference manual) and returns
-                      -- the previous value of the step multiplier.
+  = GCStop               -- ^ stops the garbage collector.
+  | GCRestart            -- ^ restarts the garbage collector
+  | GCCollect            -- ^ performs a full garbage-collection cycle.
+  | GCCount              -- ^ returns the current amount of memory (in
+                         -- Kbytes) in use by Lua.
+  | GCCountb             -- ^ returns the remainder of dividing the current
+                         -- amount of bytes of memory in use by Lua by 1024.
+  | GCStep CInt          -- ^ performs an incremental step of garbage
+                         -- collection, corresponding to the allocation of
+                         -- @stepsize@ Kbytes.
+  | GCInc CInt CInt CInt -- ^ Changes the collector to incremental mode
+                         -- with the given parameters (see
+                         -- <https://www.lua.org/manual/5.4/manual.html#2.5.1
+                         -- §2.5.1>). Returns the previous mode
+                         -- (@LUA_GCGEN@ or @LUA_GCINC@).
+                         -- Parameters: pause, stepmul, and stepsize.
+  | GCGen CInt CInt      -- ^ Changes the collector to generational mode
+                         -- with the given parameters (see
+                         -- <https://www.lua.org/manual/5.4/manual.html#2.5.2
+                         -- §2.5.2>). Returns the previous mode
+                         -- (@LUA_GCGEN@ or @LUA_GCINC@).
   | GCIsRunning       -- ^ returns a boolean that tells whether the
                       -- collector is running (i.e., not stopped).
   deriving (Eq, Ord, Show)
@@ -294,18 +294,19 @@
   GCCollect       -> LUA_GCCOLLECT
   GCCount         -> LUA_GCCOUNT
   GCCountb        -> LUA_GCCOUNTB
-  GCStep          -> LUA_GCSTEP
-  GCSetPause {}   -> LUA_GCSETPAUSE
-  GCSetStepMul {} -> LUA_GCSETSTEPMUL
+  GCStep _        -> LUA_GCSTEP
   GCIsRunning     -> LUA_GCISRUNNING
+  GCGen {}        -> LUA_GCGEN
+  GCInc {}        -> LUA_GCINC
 {-# INLINABLE toGCcode #-}
 
 -- | Returns the data value associated with a GCControl command.
-toGCdata :: GCControl -> CInt
+toGCdata :: GCControl -> (CInt, CInt, CInt)
 toGCdata = \case
-  GCSetPause p   -> p
-  GCSetStepMul m -> m
-  _              -> 0
+  GCStep stepsize         -> (stepsize, 0, 0)
+  GCGen minormul majormul -> (minormul, majormul, 0)
+  GCInc pause mul size    -> (pause, mul, size)
+  _                       -> (0, 0, 0)
 {-# INLINABLE toGCdata #-}
 
 --
diff --git a/src/HsLua/Core/Userdata.hs b/src/HsLua/Core/Userdata.hs
--- a/src/HsLua/Core/Userdata.hs
+++ b/src/HsLua/Core/Userdata.hs
@@ -12,7 +12,7 @@
 Convenience functions to convert Haskell values into Lua userdata.
 -}
 module HsLua.Core.Userdata
-  ( newhsuserdata
+  ( newhsuserdatauv
   , newudmetatable
   , fromuserdata
   , putuserdata
@@ -21,7 +21,7 @@
 import HsLua.Core.Types (LuaE, Name (..), StackIndex, liftLua, fromLuaBool)
 import Lua.Userdata
   ( hslua_fromuserdata
-  , hslua_newhsuserdata
+  , hslua_newhsuserdatauv
   , hslua_newudmetatable
   , hslua_putuserdata
   )
@@ -29,9 +29,10 @@
 
 -- | Creates a new userdata wrapping the given Haskell object. The
 -- userdata is pushed to the top of the stack.
-newhsuserdata :: forall a e. a -> LuaE e ()
-newhsuserdata = liftLua . flip hslua_newhsuserdata
-{-# INLINABLE newhsuserdata #-}
+newhsuserdatauv :: forall a e. a -> Int -> LuaE e ()
+newhsuserdatauv x nuvalue = liftLua $ \l ->
+  hslua_newhsuserdatauv l x (fromIntegral nuvalue)
+{-# INLINABLE newhsuserdatauv #-}
 
 -- | Creates and registers a new metatable for a userdata-wrapped
 -- Haskell value; checks whether a metatable of that name has been
diff --git a/test/HsLua/Core/ErrorTests.hs b/test/HsLua/Core/ErrorTests.hs
--- a/test/HsLua/Core/ErrorTests.hs
+++ b/test/HsLua/Core/ErrorTests.hs
@@ -52,11 +52,11 @@
         throwTypeMismatchError "number" Lua.top :: Lua ()
     , "got unnamed userdata" =:
       "number expected, got userdata" `shouldBeErrorMessageOf` do
-        Lua.newhsuserdata ()
+        Lua.newhsuserdatauv () 0
         throwTypeMismatchError "number" Lua.top :: Lua ()
     , "named userdata" =:
       "Bar expected, got Foo" `shouldBeErrorMessageOf` do
-        Lua.newhsuserdata ()
+        Lua.newhsuserdatauv () 0
         Lua.newudmetatable "Foo"
         Lua.setmetatable (Lua.nth 2)
         throwTypeMismatchError "Bar" Lua.top :: Lua ()
diff --git a/test/HsLua/Core/PackageTests.hs b/test/HsLua/Core/PackageTests.hs
--- a/test/HsLua/Core/PackageTests.hs
+++ b/test/HsLua/Core/PackageTests.hs
@@ -67,8 +67,9 @@
       in Just testModule `shouldBeResultOf` do
         Lua.openlibs
         Lua.preloadhs "test.module" (1 <$ Lua.pushstring testModule)
+        oldtop <- gettop
         pushLuaExpr "require 'test.module'"
-        Lua.tostring Lua.top
+        Lua.tostring (oldtop + 1)
     ]
 
   ]
diff --git a/test/HsLua/Core/UserdataTests.hs b/test/HsLua/Core/UserdataTests.hs
--- a/test/HsLua/Core/UserdataTests.hs
+++ b/test/HsLua/Core/UserdataTests.hs
@@ -12,7 +12,7 @@
 
 import HsLua.Core (getfield, pushboolean, setmetatable, tostring)
 import HsLua.Core.Userdata
-  (fromuserdata, newhsuserdata, newudmetatable, putuserdata)
+  (fromuserdata, newhsuserdatauv, newudmetatable, putuserdata)
 import HsLua.Core.Types (nth, top)
 import Test.Tasty.HsLua ((=:), shouldBeResultOf)
 import Test.Tasty (TestTree, testGroup)
@@ -28,7 +28,7 @@
 
   , "get back pushed value" =:
     Just (Sample 0 "zero") `shouldBeResultOf` do
-      newhsuserdata (Sample 0 "zero")
+      newhsuserdatauv (Sample 0 "zero") 0
       newudmetatable "Sample"
       setmetatable (nth 2)
       fromuserdata top "Sample"
@@ -40,14 +40,14 @@
 
   , "fail on wrong userdata" =:
     (Nothing :: Maybe Sample) `shouldBeResultOf` do
-      newhsuserdata (5 :: Integer)
+      newhsuserdatauv (5 :: Integer) 0
       newudmetatable "Integer"
       setmetatable (nth 2)
       fromuserdata top "Sample"
 
   , "change wrapped value" =:
     Just (Sample 1 "a") `shouldBeResultOf` do
-      newhsuserdata (Sample 5 "five")
+      newhsuserdatauv (Sample 5 "five") 0
       newudmetatable "Sample"
       setmetatable (nth 2)
       True <- putuserdata top "Sample" (Sample 1 "a")
@@ -55,7 +55,7 @@
 
   , "change fails on wrong name" =:
     Just (Sample 2 "b") `shouldBeResultOf` do
-      newhsuserdata (Sample 2 "b")
+      newhsuserdatauv (Sample 2 "b") 0
       newudmetatable "Sample"
       setmetatable (nth 2)
       False <- putuserdata top "WRONG" (Sample 3 "c")
diff --git a/test/HsLua/CoreTests.hs b/test/HsLua/CoreTests.hs
--- a/test/HsLua/CoreTests.hs
+++ b/test/HsLua/CoreTests.hs
@@ -193,12 +193,12 @@
 
   , "setting and getting a global works" =:
     Just "Fisch" `shouldBeResultOf` do
-      newhsuserdata ()
+      newhsuserdatauv () 1
       pushstring "Fisch"
-      setuservalue (nth 2)
+      setiuservalue (nth 2) 1
 
       -- get uservalue again
-      TypeString <- getuservalue top
+      TypeString <- getiuservalue top 1
       tostring top
 
   , "can push and receive a thread" ?: do
@@ -294,12 +294,12 @@
         Just "Berlin" `shouldBeResultOf` do
           Lua.pushstring "Berlin"
           cityref <- Lua.ref Lua.registryindex
-          Lua.pushnil -- dummy op
+          Lua.settop 0 -- remove all elements from stack
           Lua.getref Lua.registryindex cityref
           Lua.tostring Lua.top
 
       , "references become invalid after unref" =:
-        Nothing `shouldBeResultOf` do
+        (Just "Heidelberg" /=) `shouldHoldForResultOf` do
           Lua.pushstring "Heidelberg"
           cityref <- Lua.ref Lua.registryindex
           Lua.unref Lua.registryindex cityref
@@ -401,7 +401,7 @@
   , testCase "garbage collection" . run $
       -- test that gc can be called with all constructors of type GCControl.
       mapM_ gc [ GCStop, GCRestart, GCCollect, GCCollect, GCCountb
-               , GCStep, GCSetPause 23, GCSetStepMul 5, GCIsRunning ]
+               , GCStep 12, GCInc 23 0 0, GCGen 5 10, GCIsRunning ]
 
   , testGroup "compare"
     [ testProperty "identifies strictly smaller values" $ compareWith (<) Lua.LT
