Nullary Data Con Wrappers are not tagged
This ticket is concerned with the tagging logic of nullary data con wrappers. Concretely, it seems we are not tagging nullary data con wrappers at their usage site. The immediate consequences of not tagging these wrappers must be considered in at least two distinct scenarios:
The data type is lifted:
- If we pass the datacon wrapper as an argument to a function untagged (tag=0), despite knowing exactly what the constructor is, in the function body we will treat the argument as a thunk (which means calling at least an extra function -- the data con _con_entry() -- which will in turn return a tagged pointer)
The data type is unlifted:
- When we pass the unlifted datacon wrapper untagged (tag=0) to a function (that must be expecting an unlifted argument), we will get a segmentation fault as in #23146 (closed). The reason is functions expect their unlifted arguments to be evaluated and tagged, and therefore do not handle the case in which the unlifted argument has tag=0.
This ticket is closely related to #23146 (closed) and I intend to fix both simultaneously. This one discusses the tagging issue in its full generality as opposed to the segfault particular to the unlifted case.
Examples
I've got a good example of this for each case.
There are two subtleties about them. Nonetheless, they showcase the bug well and we must handle this correctly regardless:
(1) Why do we even have a wrapper for a nullary data con? Answer: because of the unlifted equality coercion the worker takes as an argument (despite this argument not being runtime relevant)
(2) The wrapper doesn't inline. (Why not?).
Unlifted case
This example will segfault when run, and it's the reproducer from #23146 (closed).
-- A.hs
{-# LANGUAGE GADTs, UnliftedDatatypes #-}
-- Unlifted, Nil worker has 1 Zero-Width argument
import GHC.Exts
import Data.Kind
import R2
fieldsSam :: NP xs -> NP xs -> Bool
fieldsSam (x' ::* xs) (y' ::* ys) = fieldsSam xs ys
fieldsSam UNil UNil = True
main :: IO ()
main = print (fieldsSam UNil UNil)
-- B.hs
{-# LANGUAGE UnliftedDatatypes #-}
module R2 where
import GHC.Exts
type NP :: [UnliftedType] -> UnliftedType
data NP xs where
UNil :: NP '[]
(::*) :: x -> NP xs -> NP (x ': xs)
Lifted case
This example will not segfault, but inspecting the cmm will show the wrappers being passed untagged and the logic to handle untagged arguments (tag == 0)
-- A.hs
{-# LANGUAGE GADTs, UnliftedDatatypes #-}
-- Lifted, 1 Zero-Width argument
import GHC.Exts
import Data.Kind
import S2
fieldsSam :: NP xs -> NP xs -> Bool
fieldsSam (x' ::* xs) (y' ::* ys) = fieldsSam xs ys
fieldsSam UNil UNil = True
main :: IO ()
main = print (fieldsSam UNil UNil)
-- B.hs
{-# LANGUAGE UnliftedDatatypes #-}
module S2 where
import Data.Kind
import GHC.Exts
type NP :: [Type] -> Type
data NP xs where
UNil :: NP '[]
(::*) :: x -> NP xs -> NP (x ': xs)