Problem: LiberateCase with two or more free variables

Manuel M. T. Chakravarty chak@cse.unsw.edu.au
Thu, 01 Mar 2001 00:19:13 +1100


LiberateCase works very nicely as long as there is only one
variable whose access has to be liberated.  However, already
with two variables, there are problems.  Given

  import PrelGHC
  import PrelBase

  data AccessPath = Cont  AccessPath
		  | Value Int

  accessLen0#, accessLen1#, accessLen2# :: AccessPath -> Int#
  accessLen0# (Value i) = case i of {I# i -> i}
  accessLen1# (Cont ap) = accessLen0# ap
  accessLen2# (Cont ap) = accessLen1# ap

  main = print $ ((demandSome (bar 0#) (foo 1# ap) (foo 5# ap)))
	 where
	   ap = Cont (Value 10)

  foo 0# x = x
  foo n# x = foo (n# -# 1#) x

  bar 0# = 0#
  bar n# = bar (n# -# 1#)

Let us assume I have the following for `demandSome':

  demandSome :: Int# -> AccessPath -> AccessPath -> Int
  demandSome n# ap1 ap2  = demandSome' n#
    where
      demandSome' n# = 
	case n# of
	  0# -> 1
	  n# | n# <# 10# -> I# (accessLen1# ap1) + demandSome' (n# -# 1#)
	  n# | otherwise -> I# (accessLen1# ap2) + demandSome' (n# -# 1#)

When compiled with 

  ghc -fglasgow-exts -O2 -fliberate-case-threshold100 -c DemandTree.hs

I get for the inner loop

  $wdemandSome'
    = \ w2 :: PrelGHC.Int# ->
	  case w2 of ds1 {
	      0 -> 1;
	      __DEFAULT ->
		  case PrelGHC.<# ds1 10 of wild4 {
		      PrelBase.True ->
			  case $wdemandSome'
				   (PrelGHC.-# ds1 1)
			  of ww { __DEFAULT ->
			  PrelGHC.+# i1 ww
			  };
		      PrelBase.False ->
			  case ap2 of wild5 {
			      Main.Value a ->
				  PrelErr.patError
				      @ PrelGHC.Int#
				      lvl_s1NP;
			      Main.Cont ap21 ->
				  case ap21 of wild6 {
				      Main.Cont a ->
					  PrelErr.patError
					      @ PrelGHC.Int#
					      lvl1_s1NO;
				      Main.Value i2 ->
					  case i2
					  of wild7 { PrelBase.I# i3 ->
					  case $wdemandSome'
						   (PrelGHC.-#
							ds1
							1)
					  of ww { __DEFAULT ->
					  PrelGHC.+# i3 ww
					  }
					  }
				  }
			  }
		  }
	  };

So, access to `ap1' has been pulled out of the loop nicely,
but `ap2' is decomposed in every recursion where the third
alternative is selected.  

However, given that we don't know whether access to `ap2'
will terminate, ghc has to be conservative here.  Now my
problem is that sometimes, *I* know that access to ap2 is
problematic.  The question is, how to tell the compiler?

The first thing that came to my mind was using `seq' like in

  demandSome :: Int# -> AccessPath -> AccessPath -> Int
  demandSome n# ap1 ap2  = demandSome' n#
    where
      demandSome' n# = 
	case n# of
	  0# -> 1
	  n# | n# <# 10# -> ap2 `seq` 
			      I# (accessLen1# ap1) + demandSome' (n# -# 1#)
	  n# | otherwise -> I# (accessLen1# ap2) + demandSome' (n# -# 1#)

However, this only makes things worse, because now I am
getting

  $wdemandSome'
    = \ w2 :: PrelGHC.Int# ->
	  case w2 of ds2 {
	      0 -> 1;
	      __DEFAULT ->
		  case PrelGHC.<# ds2 10
		  of wild4 {
		      PrelBase.True ->
			  case PrelGHC.seq#
				   @ Main.AccessPath
				   ap2
			  of ds3 {
			      0 ->
				  __coerce
				      PrelGHC.Int#
				  (PrelErr.seqError
				       @ PrelBase.Int);
			      __DEFAULT ->
				  case $wdemandSome'
					   (PrelGHC.-#
						ds2
						1)
				  of ww { __DEFAULT ->
				  PrelGHC.+# i1 ww
				  }
			  };
		      PrelBase.False ->
			  case ap2 of wild5 {
			      Main.Value a ->
				  PrelErr.patError
				      @ PrelGHC.Int#
				      lvl_s1PF;
			      Main.Cont ap21 ->
				  case ap21
				  of wild6 {
				      Main.Cont a ->
					  PrelErr.patError
					      @ PrelGHC.Int#
					      lvl1_s1PE;
				      Main.Value i2 ->
					  case i2
					  of wild7 { PrelBase.I# i3 ->
					  case $wdemandSome'
						   (PrelGHC.-#
							ds2
							1)
					  of ww { __DEFAULT ->
					  PrelGHC.+#
					      i3
					      ww
					  }
					  }
				  }
			  }
		  }
	  };

The `seq' is dynamically evaluated (slowing down the second
alternative) instead of leading to liberation of `case ap2
of wild5 {...}'.

Even if the trick with `seq' would have worked, it would
only have solved half of the problem, because I would still
have `case ap21 of wild6 {...}' in the inner loop.
Sometimes, I can't do much else than applying `seq', namely
when `AccessPath' is imported abstractly by the module
including the recursion.

I have this problem in the array library, only worse.  A
frequently executed recursive definition has *five*
variables whose unboxing should be liberated (which includes
the traversal of an access path like the one above).
Furthermore, the function corresponding to `accessLen1' is
overloaded and the type on which it operates is abstract.
The lack of case liberation, unfortunately, causes a massive
performance loss.

Cheers,
Manuel