safe for space ... and IntInf
Stephen Weeks
MLton@research.nj.nec.com
Tue, 27 Jun 2000 18:52:19 -0700 (PDT)
> I was thinking about the flattener, and realized that if you don't require
> the flatten-ness of a procedure result to be the same as that of any
> procedures it tail-calls to you won't be safe-for-space. The problem is that
> unlike other coercions, where you only coerce up a finite lattice, here you
> can have loops. I.e., if f tail-calls g and the result of f is flat and that
> of g is non-flat and g tail-calls f, then both of those tail-calls will turn
> into non-tail-calls and you will use unbounded space.
You are correct. Here is your example fleshed out.
fun isEven x = x mod 2 = 0
fun isOdd x = x mod 2 = 1
fun f(x: int): int * int = if isOdd x andalso x > 0 then g(x - 1) else (13, 15)
and g(x: int): int * int = if isEven x then f(x - 1) else (17, 19)
val _ = f 101
> Is this restriction currently in place (I.e., are we safe for space now)?
Yes. Right now, the flattener never changes tail calls into non-tail
calls. However, other Cps simplification passes, like useless.fun,
do turn tail calls into nontail calls. I think useless is ok because
it has the one-way property (you can only coerce something that's
useful into something that's useless, but not vice versa).
Your mail also came at a fortunate time, as I was trying to track down
a seg fault I was getting in the smith-normal-form regression test.
For stress testing, I turned off all the cps simplify passes (except
for poly equal) and ran the regressions. smith-normal-form failed
with a seg fault when compiled normally, and failed with an assertion
failure in IntInf_do_neg when compiled -g. The assertion failure was
right at the beginning, checking that the frontier is in the expected
place.
assert(frontier == (pointer)&bp->limbs[bp->card - 1]);
I'd been tracking this bug for a couple hours when I received your
mail about the flattener. Do you see the connection? :-) As a
reminder, here is the code for bigNegate
fun bigNegate (arg: bigInt): bigInt =
if Prim.isSmall arg
then let val argw = Prim.toWord arg
in if argw = badw
then negBad
else Prim.fromWord (Word.- (0w2, argw))
end
else Prim.~ (arg, allocate (1 + bigSize arg))
The problem is, when the flattener is turned off, there is an
allocation in between the call to allocate and the Prim.~ call. The
argument tuple allocation screws everything up. So, we are relying on
the flattener for correctness of the IntInf implementation. Any ideas
on how to improve the implementation to remove this reliance, or at
least put an assert somewhere to avoid falling prey to this bug again?