2020-07-27
More implementation
Let's see if we can't get that inference graph working. On Thursday, we had a
problem in if.fut with mem_37 never going out of scope.
Here is the problem:
if cond_29 then {mem_37, xs_27} else { let {mem mem_40} = alloc(bytes_35) -- res_31 : [n_18]i32@@mem_40-> -- {base: [n_18]; contiguous: True; LMADs: [{offset: 0i32; strides: [1i32]; -- rotates: [0i32]; shape: [n_18]; -- permutation: [0]; -- monotonicity: [Inc]}]} let {[n_18]i32 res_31} = iota32(n_18, 0i32, 1i32) in {mem_40, res_31} }
Because mem_37 is returned from the then-branch, and because a body return is
not part of a statement, mem_37 is never last used in the reported last-use
map. Furthermore, because bodies in if-expressions don't really have a VName
associated with it, the only alternative is to associate the last-use of
mem_37 with the statement binding to the if expression, but then we have no
way of distinguishing between which branch a memory block is last-used in…
In other words, I'm not sure I can actually even do the if-optimization I was
working on, with the current last-use representation.
Another problem is that mem_37 really should be reported as last-used at some
point. Otherwise we'll never be able to reuse it later. I think the problem is
that my last-use analyseExp is not correct. Let's try to investigate.
Currently, analyseExp takes as arguments a LastUseMap, a UsedNames and and
expression with aliasing information and returns (Names, LastUseMap,
UsedNames). Some questions immediately present themselves: What is the
LastUseMap given as input used for? What is the difference between the Names
and the UsedNames results?
It doesn't look the like LastUseMap input is really used for anything. It's
convenient that analyseStm takes a (LastUseMap, UsedNames) as input and as
output, because we can then foldr over it as part of analyseKernelBody, but
shouldn't we really be calling analyseBody inside analyseKernelBody, or at
least analyseStms instead of manually folding over the statements? I think
yes.
The one problem with changing this is that I haven't made any tests for the last-use analysis… Let's disregard that for now, and boldly move forward.
Next question: What is the purpose of Names and UsedNames inside
analyseExp? The answer here relates to the fact that expressions can contain
bodies of code, and the names that are last-used inside the body of code is not
last-used in the outside expression, but it should be part of the set of used
names. One good question however: Perhaps values last-used in a nested
expression should also be mapped to the outside expression? Let's investigate
with an example:
let z = -- last use of xs and ys? if ... then let x = xs[0] -- last use of xs in x else let y = ys[0] -- last use of ys in y
The question here is, should we amend the LastUseMap to contain (z, [xs,
ys]) in addition to (x, [xs]) and (y, [ys])? Doing so would mean that the
LastUseMap is no longer context-free or unambiguous: ys would appear as
last-used in multiple places, and only by knowing the context (the last-use of
ys associated with z means that ys is last-used inside the block used to
compute z) can we use the map correctly.
What would it mean for the following code:
let z = -- last use of xs and ys? if ... then xs -- last use of xs else ys -- last use of ys
Here, we cannot actually, in the LastUseMap, associate xs with the correct
place inside the then-branch, but we can associate it with z.
A hybrid approach is also possible, where values that are returned from bodies are reported as last-used in the parent statement, but otherwise not.
Upon looking at ReuseAllocations, I think I need the hybrid approach for
now. In particular, when creating the interference map for an if-expression, I
have no way of knowing when a value that is returned from a body is last used,
if they don't appear anywhere.
Also, why is mem_40 being reported as in use after the if expression in
which it's created? It should never be allowed to leave the scope.
I need to clear my mind. Let's try to start over with the interference graph algorithm.
Conservative rule: for let p = exp, everything inside exp should interfere
with everything in p
InUse rule: For simplicity InUse is only added to from p, and removed
from using the LastUseMap of p.
It was convenient to have analyseStm and analyseBody return both InUse and
LastUsed because those encompass every name referenced inside.
We used that to create the extra interference necessary to handle loops
correctly. That corresponds with out conservative rule. Is there a general way
to do this that doesn't require returning both InUse and LastUsed? Really, we're
only interested in the graph from inside a body (since any new allocations
should not leak to the outside(?)), and whatever is in-use at the end. Because
of the loop, I think it makes sense to still return InUse and LastUsed, but
setting InUse_new = InUse_inner ∩ InUse_prev going forward. What about
nested loops though? That means we still have to keep track of LastUsed as
well, I think… And those things that that were not in the intersection between
InUse_inner and InUse_prev… Perhaps it's easier to just have InUse and
UsedNames? That should make the design a bit simpler, and since we've already
given up on my if optimization, I don't think we need LastUsed. Let's try it…
analyseStm :: LastUseMap -> InUse -> Stm -> (InUse, UsedNames, MemGraph)
analyseStm lumap inuse0 (let p = exp) =
let new_mems = memory blocks referenced in p
let graph = inuse ↔ inuse
let lus0 = lookup p in lumap
let lus = memory blocks referenced in lus0
let inuse = (inuse0 ∪ mems) ∩ lus
if exp is a loop with body b then
let (inuse', used, graph') = analyseBody lumap b
let graph'' = graph ∪ graph' ∪ (inuse ↔ used)
let inuse'' = inuse' ∩ inuse
in (inuse'', used ∪ inuse, graph'')
if exp is an if with bodies b1, b2 then
let (inuse_then, used_then, graph_then) = analyseBody lumap b1
let (inuse_else, used_else, graph_else) = analyseBody lumap b2
let used = used_then ∪ used_else ∪ inuse
let inuse' = (inuse_then ∪ inuse_else) ∩ inuse
let graph' = graph ∪ graph_then ∪ graph_else ∪ (inuse' ↔ (used_then ∪ used_else)) <-
in (inuse', used, graph')
else
(inuse, inuse, graph)
argh, problems. Right now, things that are inuse before, but not inside an if, is not inuse after the if.