GHC doesn't optimise `()` constraint synonym
Summary
Consider the following example:
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# OPTIONS_GHC -dno-typeable-binds -O -ddump-simpl -ddump-stg-final #-}
module Test where
import Data.Kind
type MyVoid = () :: Constraint
{-# NOINLINE foo #-} -- We use `NOINLINE` to simulate large functions.
foo :: MyVoid => Int -> Int
foo = abs
{-# NOINLINE bar #-}
bar :: [Int] -> [Int]
bar = map foo
MyVoid
constraint is kept all the way through CodeGen!
==================== Tidy Core ====================
Result size of Tidy Core
= {terms: 9, types: 12, coercions: 0, joins: 0/0}
-- RHS size: {terms: 2, types: 1, coercions: 0, joins: 0/0}
foo [InlPrag=NOINLINE] :: MyVoid => Int -> Int
[GblId, Arity=2, Str=<A><SP(U)>, Cpr=m1, Unf=OtherCon []]
foo = \ _ [Occ=Dead] -> GHC.Num.$fNumInt_$cabs
-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
bar1_rxR :: Int -> Int
[GblId, Arity=1, Str=<SP(U)>, Cpr=m1, Unf=OtherCon []]
bar1_rxR = foo GHC.Classes.(%%)
-- RHS size: {terms: 2, types: 2, coercions: 0, joins: 0/0}
bar [InlPrag=NOINLINE] :: [Int] -> [Int]
[GblId, Arity=1, Str=<SU>, Unf=OtherCon []]
bar = map @Int @Int bar1_rxR
==================== Final STG: ====================
Test.foo [InlPrag=NOINLINE]
:: Test.MyVoid => GHC.Types.Int -> GHC.Types.Int
[GblId, Arity=2, Str=<A><SP(U)>, Cpr=m1, Unf=OtherCon []] =
{} \r [$d(%%)_sAJ eta_B0] GHC.Num.$fNumInt_$cabs eta_B0;
bar1_rxR :: GHC.Types.Int -> GHC.Types.Int
[GblId, Arity=1, Str=<SP(U)>, Cpr=m1, Unf=OtherCon []] =
{} \r [eta_B0] Test.foo GHC.Classes.(%%) eta_B0;
Test.bar [InlPrag=NOINLINE] :: [GHC.Types.Int] -> [GHC.Types.Int]
[GblId, Arity=1, Str=<SU>, Unf=OtherCon []] =
{} \r [eta_B0] GHC.Base.map bar1_rxR eta_B0;
While if we manually replace MyVoid
with ()
we get:
==================== Tidy Core ====================
Result size of Tidy Core
= {terms: 5, types: 8, coercions: 0, joins: 0/0}
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
foo [InlPrag=NOINLINE] :: Int -> Int
[GblId, Arity=1, Str=<SP(U)>, Cpr=m1, Unf=OtherCon []]
foo = GHC.Num.$fNumInt_$cabs
-- RHS size: {terms: 2, types: 2, coercions: 0, joins: 0/0}
bar [InlPrag=NOINLINE] :: [Int] -> [Int]
[GblId, Arity=1, Str=<SU>, Unf=OtherCon []]
bar = map @Int @Int foo
==================== Final STG: ====================
Test.foo [InlPrag=NOINLINE] :: GHC.Types.Int -> GHC.Types.Int
[GblId, Arity=1, Str=<SP(U)>, Cpr=m1, Unf=OtherCon []] =
{} \r [eta_B0] GHC.Num.$fNumInt_$cabs eta_B0;
Test.bar [InlPrag=NOINLINE] :: [GHC.Types.Int] -> [GHC.Types.Int]
[GblId, Arity=1, Str=<SU>, Unf=OtherCon []] =
{} \r [eta_B0] GHC.Base.map Test.foo eta_B0;
This is a little surprising. I would expect both to compile as in the second version.
I've found this in GHC itself where we use HasDebugCallStack = () :: Constraint
in non DEBUG builds. HasDebugCallStack
can be seen in Core/STG dumps and some functions using it are too big to get a worker that avoids passing a dead (%%)
argument so it has some impact on codegen.