Skip to content

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)

CC @simonpj @bgamari @AndreasK

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