GHC API: getting the unfolding of a "strange" Id

Dear list, I'm using the GHC API to get Core Expressions from haskell interface (.hi) files, and have encountered a strange kind of 'Id' for which I can't seem to get the unfolding: The properties of this 'Id' are the following: - varName: $fNumFixed2 - IdDetails: VannillaId - Pretty print of IdInfo.inlinePragInfo: [Always] - IdInfo.unfoldingInfo: NoUnfolding The source file which gives rise to this 'Id' is: https://github.com/christiaanb/clash-prelude/blob/master/src/CLaSH/Sized/Fix... As you can see, the file is compiled with: -fexpose-all-unfoldings The '$f' prefix seems to indicate that the 'Id' is a Dictionary Function, so I was expecting its 'IdInfo.unfoldingInfo' to be a 'DFunUnfolding'. Given that pretty printing 'inlinePragInfo' gives me '[Always]', I would expect to have a usable 'Unfolding' in general. How can GHC inline the body of this 'Id' if it has no unfolding? Also, if I compile the file with "-O0 -fno-omit-interface-pragmas", the $fNumFixed2 'Id' is no longer included in the interface file. At positions where "$fNumFixed2" was originally used, a equality constraint can eventually be found: Using -O: (CLaSH.Sized.Fixed.$fNumFixed2 @(GHC.TypeLits.+ 4 4) Using -O0 -fno-omit-interface-pragmas: (GHC.Types.Eq# @GHC.Types.Bool @(GHC.TypeLits.<=? 1 (GHC.TypeLits.+ 4 4)) @GHC.Types.True _CO_) So my question is, what kind of "strange" 'Id' is this $fNumFixed2? And how can I get the Core expression for this 'Id' from the interface file? And if I can't get it from the interface file, is there a straightforward way to generate it from the 'Id'? Cheers, Christiaan

After some more analysis, I found out the following two details: The output of 'ghc -show-iface': be3312a893800a4aa077856449084da7 $fNumFixed2 :: forall (n::GHC.TypeLits.Nat). (GHC.TypeLits.<=) 1 n {- Strictness: b -} $fNumFixed2 is only included in the interface file because of -fexpose-all-unfoldings. And appearently, the inlinePragInfo is by default set to AlwaysInline when an Id is included in the interface file. Additionally, the Id seems to be introduced by the strictness analysis, as compiling with -fno-strictness makes the Id go away. So I guess I should rephrase my question abit: Why does the strictness analysis phase introduce binders for which no unfoldings are generated in the interface file? Even when -fexpose-all-unfoldings is set. -- Christiaan On Wed, Mar 26, 2014 at 5:40 PM, Christiaan Baaij < christiaan.baaij@gmail.com> wrote:
Dear list,
I'm using the GHC API to get Core Expressions from haskell interface (.hi) files, and have encountered a strange kind of 'Id' for which I can't seem to get the unfolding:
The properties of this 'Id' are the following: - varName: $fNumFixed2 - IdDetails: VannillaId - Pretty print of IdInfo.inlinePragInfo: [Always] - IdInfo.unfoldingInfo: NoUnfolding
The source file which gives rise to this 'Id' is: https://github.com/christiaanb/clash-prelude/blob/master/src/CLaSH/Sized/Fix... As you can see, the file is compiled with: -fexpose-all-unfoldings
The '$f' prefix seems to indicate that the 'Id' is a Dictionary Function, so I was expecting its 'IdInfo.unfoldingInfo' to be a 'DFunUnfolding'. Given that pretty printing 'inlinePragInfo' gives me '[Always]', I would expect to have a usable 'Unfolding' in general. How can GHC inline the body of this 'Id' if it has no unfolding?
Also, if I compile the file with "-O0 -fno-omit-interface-pragmas", the $fNumFixed2 'Id' is no longer included in the interface file. At positions where "$fNumFixed2" was originally used, a equality constraint can eventually be found:
Using -O: (CLaSH.Sized.Fixed.$fNumFixed2 @(GHC.TypeLits.+ 4 4)
Using -O0 -fno-omit-interface-pragmas: (GHC.Types.Eq# @GHC.Types.Bool @(GHC.TypeLits.<=? 1 (GHC.TypeLits.+ 4 4)) @GHC.Types.True _CO_)
So my question is, what kind of "strange" 'Id' is this $fNumFixed2? And how can I get the Core expression for this 'Id' from the interface file? And if I can't get it from the interface file, is there a straightforward way to generate it from the 'Id'?
Cheers,
Christiaan

From what you say, in a subsequent message, about the strictness being "b", that means the strictness analyser has decided that $fNumFixed2 is bottom (i.e. diverges). So there's no point in exposing the unfolding, because (one way or another) it's an infinite loop, so there's no point in optimising it.
It's unusual to have a bottom dictionary; you must be using undecidable instances or something. ("Scrap your boilerplate with class" describes why recursive dictionaries are good.) Bur apparently you have. It's arguable that with -fexpose-all-unfoldings we should expose even bottom values. If you have a reason for wanting that, it'd be an easy change to make, I think. Simon | -----Original Message----- | From: Glasgow-haskell-users [mailto:glasgow-haskell-users- | bounces@haskell.org] On Behalf Of Christiaan Baaij | Sent: 26 March 2014 16:41 | To: glasgow-haskell-users | Subject: GHC API: getting the unfolding of a "strange" Id | | Dear list, | | I'm using the GHC API to get Core Expressions from haskell interface | (.hi) files, and have encountered a strange kind of 'Id' for which I | can't seem to get the unfolding: | | The properties of this 'Id' are the following: | - varName: $fNumFixed2 | - IdDetails: VannillaId | - Pretty print of IdInfo.inlinePragInfo: [Always] | - IdInfo.unfoldingInfo: NoUnfolding | | The source file which gives rise to this 'Id' is: | https://github.com/christiaanb/clash- | prelude/blob/master/src/CLaSH/Sized/Fixed.hs | As you can see, the file is compiled with: -fexpose-all-unfoldings | | The '$f' prefix seems to indicate that the 'Id' is a Dictionary Function, | so I was expecting its 'IdInfo.unfoldingInfo' to be a 'DFunUnfolding'. | Given that pretty printing 'inlinePragInfo' gives me '[Always]', I would | expect to have a usable 'Unfolding' in general. | How can GHC inline the body of this 'Id' if it has no unfolding? | | Also, if I compile the file with "-O0 -fno-omit-interface-pragmas", the | $fNumFixed2 'Id' is no longer included in the interface file. | At positions where "$fNumFixed2" was originally used, a equality | constraint can eventually be found: | | Using -O: | (CLaSH.Sized.Fixed.$fNumFixed2 | @(GHC.TypeLits.+ 4 4) | | Using -O0 -fno-omit-interface-pragmas: | (GHC.Types.Eq# | @GHC.Types.Bool | @(GHC.TypeLits.<=? 1 (GHC.TypeLits.+ 4 4)) | @GHC.Types.True | _CO_) | | So my question is, what kind of "strange" 'Id' is this $fNumFixed2? | And how can I get the Core expression for this 'Id' from the interface | file? | And if I can't get it from the interface file, is there a straightforward | way to generate it from the 'Id'? | | Cheers, | | Christiaan | | | _______________________________________________ | Glasgow-haskell-users mailing list | Glasgow-haskell-users@haskell.org | http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

Hello Simon,
Thank you for your reply, I am indeed using undecidable instances.
The type of the "offending" dictionary is: forall (n::GHC.TypeLits.Nat). (GHC.TypeLits.<=) 1 n
Indeed, part of the context for my 'Num' instance is: 1 <= n
Where GHC.TypeLits defines: type x <= y = (x <=? y) ~ True
I don't really get why this GHC.TypeLits constraint is a bottom dictionary...
Is it because all Coercions/Constraints are bottom?
Is there perhaps a set of flags I can use so that I can see the Core term corresponding to CLaSH.Sized.Fixed.$fNumFixed2 during compilation?
As for exposing bottom values with -fexpose-all-unfoldings, my use-case is specific, but not too specific I guess.
What I do is use whole-program transformation to convert Core to the hardware description language VHDL.
As such, any identifier without an unfolding must be considered a black box, for which the compiler must have builtin knowledge.
I understand that primitive operators defined in GHC.Prim, such as '+#', don't have an unfolding.
And indeed, for those operators I can generate equivalently behaving VHDL.
But it becomes annoying when any user code can give rise to potential identifiers with no unfolding.
I think that any of the Haskell->Javascript projects that start from Core would have the same issues.
Hence my believe that my use-case is not too specific.
Also, the flag is called -fexpose-all-unfoldings, not -fexpose-all-unfoldings-except-bottom-values ;-)
-- Christiaan
On Mar 28, 2014, at 10:08 AM, Simon Peyton Jones
From what you say, in a subsequent message, about the strictness being "b", that means the strictness analyser has decided that $fNumFixed2 is bottom (i.e. diverges). So there's no point in exposing the unfolding, because (one way or another) it's an infinite loop, so there's no point in optimising it.
It's unusual to have a bottom dictionary; you must be using undecidable instances or something. ("Scrap your boilerplate with class" describes why recursive dictionaries are good.) Bur apparently you have.
It's arguable that with -fexpose-all-unfoldings we should expose even bottom values. If you have a reason for wanting that, it'd be an easy change to make, I think.
Simon
| -----Original Message----- | From: Glasgow-haskell-users [mailto:glasgow-haskell-users- | bounces@haskell.org] On Behalf Of Christiaan Baaij | Sent: 26 March 2014 16:41 | To: glasgow-haskell-users | Subject: GHC API: getting the unfolding of a "strange" Id | | Dear list, | | I'm using the GHC API to get Core Expressions from haskell interface | (.hi) files, and have encountered a strange kind of 'Id' for which I | can't seem to get the unfolding: | | The properties of this 'Id' are the following: | - varName: $fNumFixed2 | - IdDetails: VannillaId | - Pretty print of IdInfo.inlinePragInfo: [Always] | - IdInfo.unfoldingInfo: NoUnfolding | | The source file which gives rise to this 'Id' is: | https://github.com/christiaanb/clash- | prelude/blob/master/src/CLaSH/Sized/Fixed.hs | As you can see, the file is compiled with: -fexpose-all-unfoldings | | The '$f' prefix seems to indicate that the 'Id' is a Dictionary Function, | so I was expecting its 'IdInfo.unfoldingInfo' to be a 'DFunUnfolding'. | Given that pretty printing 'inlinePragInfo' gives me '[Always]', I would | expect to have a usable 'Unfolding' in general. | How can GHC inline the body of this 'Id' if it has no unfolding? | | Also, if I compile the file with "-O0 -fno-omit-interface-pragmas", the | $fNumFixed2 'Id' is no longer included in the interface file. | At positions where "$fNumFixed2" was originally used, a equality | constraint can eventually be found: | | Using -O: | (CLaSH.Sized.Fixed.$fNumFixed2 | @(GHC.TypeLits.+ 4 4) | | Using -O0 -fno-omit-interface-pragmas: | (GHC.Types.Eq# | @GHC.Types.Bool | @(GHC.TypeLits.<=? 1 (GHC.TypeLits.+ 4 4)) | @GHC.Types.True | _CO_) | | So my question is, what kind of "strange" 'Id' is this $fNumFixed2? | And how can I get the Core expression for this 'Id' from the interface | file? | And if I can't get it from the interface file, is there a straightforward | way to generate it from the 'Id'? | | Cheers, | | Christiaan | | | _______________________________________________ | Glasgow-haskell-users mailing list | Glasgow-haskell-users@haskell.org | http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

I don't really get why this GHC.TypeLits constraint is a bottom dictionary... Is it because all Coercions/Constraints are bottom? Is there perhaps a set of flags I can use so that I can see the Core term corresponding to CLaSH.Sized.Fixed.$fNumFixed2 during compilation?
I guess I straight away answer my own question, sorry for the noise: CLaSH.Sized.Fixed.$fNumFixed2 :: forall (n_a5SH :: GHC.TypeLits.Nat). 1 GHC.TypeLits.<= n_a5SH [GblId, Str=DmdType b] CLaSH.Sized.Fixed.$fNumFixed2 = \ (@ (n_a5SH :: GHC.TypeLits.Nat)) -> Control.Exception.Base.absentError @ (1 GHC.TypeLits.<= n_a5SH) "ww_s9JT{v} [lid] 1 base:GHC.TypeLits.<={tc r1W} n{tv a5SH} [tv]"# I must say... I have never seen, in any of my programs, the error that Control.Exception.Base.absentError is supposed to give: absentError s = error ("Oops! Entered absent arg " ++ unpackCStringUtf8# s) -- Christiaan

These "absent" thunks appear when the strictness analyser works out that a function does not use an argument at all. Suppose 'f' does not use its first argument. Then a call f (...big expression...) will be replaced with f (absentError "blah") Such thunks almost invariably turn out to be dead code in the end, and are discarded. It's very unusual for them to survive to the end of compilation, let alone as a top-level value. But it's not impossible. Just for curiosity, can you compile with -dverbose-core2core, and leave the results somewhere I can see them? In short, it doesn't look as if there is anything wrong. If there's a good reason you want to see the unfolding anyway, that's not impossible, but at least you understand what is happening now. Simon | -----Original Message----- | From: Christiaan Baaij [mailto:christiaan.baaij@gmail.com] | Sent: 28 March 2014 09:57 | To: Simon Peyton Jones | Cc: glasgow-haskell-users | Subject: Re: GHC API: getting the unfolding of a "strange" Id | | | > I don't really get why this GHC.TypeLits constraint is a bottom | dictionary... | > Is it because all Coercions/Constraints are bottom? | > Is there perhaps a set of flags I can use so that I can see the Core | term corresponding to CLaSH.Sized.Fixed.$fNumFixed2 during compilation? | | | I guess I straight away answer my own question, sorry for the noise: | | CLaSH.Sized.Fixed.$fNumFixed2 | :: forall (n_a5SH :: GHC.TypeLits.Nat). 1 GHC.TypeLits.<= n_a5SH | [GblId, Str=DmdType b] | CLaSH.Sized.Fixed.$fNumFixed2 = | \ (@ (n_a5SH :: GHC.TypeLits.Nat)) -> | Control.Exception.Base.absentError | @ (1 GHC.TypeLits.<= n_a5SH) | "ww_s9JT{v} [lid] 1 base:GHC.TypeLits.<={tc r1W} n{tv a5SH} [tv]"# | | I must say... I have never seen, in any of my programs, the error that | Control.Exception.Base.absentError is supposed to give: | absentError s = error ("Oops! Entered absent arg " ++ | unpackCStringUtf8# s) | | -- Christiaan

Thank you for the explanation, I think I pretty much understand what's going on now.
Indeed, nothing went wrong, it was just that I thought that the flag -fexpose-all-unfoldings would indeed put _all_ unfoldings in the interface file: even unfoldings that are bottom values.
For my use-case, I can just compile the file with -fno-strictness so that this top-level bottom value is never created in the first instance.
Here are the -dverbose-core2core files:
Compiled with no additional / default flags: https://raw.githubusercontent.com/christiaanb/clash-prelude/gh-pages/noflags...
Compiled with -fexpose-all-unfoldings: https://raw.githubusercontent.com/christiaanb/clash-prelude/gh-pages/allunfo...
Compiled with -fexpose-all-unfoldings -fno-strictness: https://raw.githubusercontent.com/christiaanb/clash-prelude/gh-pages/allunfo...
-- Christiaan
On Apr 1, 2014, at 1:11 PM, Simon Peyton Jones
These "absent" thunks appear when the strictness analyser works out that a function does not use an argument at all. Suppose 'f' does not use its first argument. Then a call f (...big expression...) will be replaced with f (absentError "blah")
Such thunks almost invariably turn out to be dead code in the end, and are discarded. It's very unusual for them to survive to the end of compilation, let alone as a top-level value. But it's not impossible.
Just for curiosity, can you compile with -dverbose-core2core, and leave the results somewhere I can see them?
In short, it doesn't look as if there is anything wrong. If there's a good reason you want to see the unfolding anyway, that's not impossible, but at least you understand what is happening now.
Simon
participants (2)
-
Christiaan Baaij
-
Simon Peyton Jones