Silly shadowing issue in StgUnarise.
Unarise walks the AST and splits certain arguments into multiple arguments (unboxed tuples) while turning other into void args. In order to do so we keep a map of in scope variables and their post-unarise representation.
However, this is excessive for the common case, that is variables representing a single runtime value. So instead of adding these to the map, we just omit them from the map and when we rewrite occurrences and there is no entry for a variable we assume it can be used as is.
Disaster strikes if shadowing comes into play:
f = \(Eta_B0 :: VoidType) x1 x2 ->
... let foo = \(Eta_B0 :: LiftedType) -> g x y Eta_B0
in ...
What goes wrong?
- We first start process
f
see it has a void argument Eta_B0 and add that to the environment and process it's rhs. - In the RHS of 'f' we start processing
foo
. We see foo has a single-rep argumentEta_B0
. Since it's single rep we don't bother adding it to the environment. - We proceed to look at the rhs of
foo
. See an occurrence ofEta_B0
and check if we need to replace it. To do so we check ifEta_B0
has an entry in the environment. And it has! We find the entry that is "left over" fromf
s arguments. Assume it's an void argument and pass no argument at runtime. - This means when compiling foo we simply don't pass the third argument to
g
. Andg
uses whatever garbage it has in the argument register and most likely eventually segfaults.
The fix is then rather simple. Whenever a new variable comes into scope either purge the environment of any mention for it if it is single rep, or if not overwrite the entry in the env (the later we already do).
Besides the fix stg lint should be able to catch things like these. Maybe I will add a check there too.