Open frankmcsherry opened 1 year ago
We have code for
- Introduce "lets at the root" as a principle we commit to for MirRelationExpr."
in explain_new/mir/mod.rs
(look for normalize_lets_in_tree
) that I'm happy to convert into a first-class Transform
implementation.
Also, I think the steps in (2) should include the introduction of multi_let_optimzier
feature flag, and we should change transforms during that process to only go through the MultiLet
route if this flag is enabled.
- Introduce "lets at the root" as a principle we commit to for MirRelationExpr."
Doesn't InlineLet
also do this? The if inlinable {
part is either inlining a Let, in which case the Let disappears, or it puts the Let into lets
. Then, outside action
, the for loop on lets
will place those one-by-one at the top of the expression.
InlineLet
may do this, but isn't documented to do so. I read through the description briefly, and it only mentions inlining of lets, rather than promoting anything. It would be good to have a CanonicalizeLets
transform that does both InlineLet
and UpdateLet
as appropriate. But certainly, if the code does already do this, then we should just use it and document it as such.
NormalizeLets
has done a part of this, but not all. In particular:
Let
has not been removed.MultiLet
yet. LetRec
could serve as a MultiLet
, but somehow we seem to have decided to have LetRec
only for actual recursion.
Materialize uses
Let
operators in expressions to allow for re-use. At the moment, they can be arbitrary AST nodes allowing for scoped re-use, but this freedom comes with some amount of stress and doesn't necessarily gain us what it would in an imperative language.Let
operator (or more often, not do this and hope thatUpdateLets
has been called recently).Let
operators at any moment of their traversal, and it may be easier to put all bindings at one location (the root, say).Let
nodes in the AST results in deeper ASTs that more often tickle recursion limits.I don't have a list of good things that nested
Let
operators would allow, other than scoping for correctness and resource reclamation. The latter doesn't really apply in our execution model, and the former seems .. to be a nuanced trade-off of footguns.So, a proposal is a pile of work in support of "put all
Let
bindings at the root". Candidate work items include:MirRelationExpr
. a. Introduce a transform that migrates allLet
expressions to the AST root; b. ensure this transform is applied after any action that might introduce an internalLet
expression; c. introduce debug checks that expressions are always in this form.MirRelationExpr::Let
to aMirRelationExpr::MultiLet
variant. a. Introduce aMultiLet
variant; panic in each transform if it is observed. b. Introduce a local transform to convert a stack ofLet
expressions to aMultiLet
and back again. c. Incrementally, implementMultiLet
behavior for transforms, remove the panic, and redirectLet
implementations to convert toMultiLet
and redirect to that implementation. d. Once allunimplemented!()
panics are removed, apply the conversion toMultiLet
as soon as possible (as part of lowering?). e. Remove theLet
variant, and perhaps renameMultiLet
toLet
.Let
from LIR a. Move allLet
bindings at the root of an LIR expression into the dataflow objects to construct. b. Move allLet
bindings to the root of MIR first, to ensure all are extracted. c. Soft panic if we observe aLet
as part of an LIR plan. b. Do the movement as part of conversion to LIR, and remove theLet
variant.These are not all equally important. I put them in the order that seems most significant to me, but they are meant to be independently achievable.
(Slack thread: https://materializeinc.slack.com/archives/C02PPB50ZHS/p1669727395057499)