-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Now with more rules] Lifting rules (lift let, lift if, if-of-if) #953
Conversation
There is something very unsatisfactory about imposing extra rules about floating things out of conditionals, as you rightly identify here. One possible approach is to be explicit about evidence. Instead of
we might think of
If the test on (One can imagine more fancy things, where instead of a uniform I wonder if something along these lines might be a more principled way of dealing with these seg-fault-y things? |
Thanks Simon! @acl33 I'll update the top comment to include some "index" examples. And then, let's try to work through the "evidence" plan. I wonder if something like this can help?
Notice that So then we might see (artist's impression, probably wrong)
|
So yes to the specialist rules and OTP; and yes to figuring out how we do evidence etc. |
I would prefer to turn it into a dataflow construct, by making the assert (and some conditionals) produce an evidence value (aka proof object, erased at runtime) that we can pass to |
@simonpj perhaps the two approaches are not so different - suppose the And, we've now defined that our I think both true and false arms of the if need to be given appropriate evidence? e.g. Also for |
Yes, that's the idea. As you say, though, we need to say how the evidence is produced. Something like this, in Haskell
Now we can say something like
Here, nothing stops us using the wrong Proof with the wrong array; and nothing stops us using the wrong array bounds. But we then can be sure that we won't lift failing operation outside its guard rails. We don't yet have case-expressions, or indeed sum types, in ksc. But I think we are going to want them. |
@@ -320,30 +320,32 @@ def __init__(self, rule: Rule, side_conditions=lambda **substs: True): | |||
def possible_filter_terms(self): | |||
return frozenset([get_filter_term(self._rule.template)]) | |||
|
|||
# This is an instance method to allow overriding in subclass(es) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cgravill this is a consequence of merging with your #921. The subclass ParsedLetLifter uses the same matches_for_possible_expr
but for each Match, must mutate the substs encoded in the apply_rewrite
lambda before passing onto the superclass. So I moved the local method apply
out here - any other ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It still seems like a good improvement, even after being hoisted. I'll try to take a look to see if there's a way to keep it more local.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"It still seems like a good improvement" - yes absolutely :). Thanks!
For me, it is quite clear that the "let" is lifted. i,e. "lift_let" is a good name. |
Naming standardized on Lift_let / lift_if, not lift_bind. |
src/python/ksc/rewrites_ast.py
Outdated
(build n (lam (i : Integer) (if p t f))) | ||
(if (gt n 0) (if p (build n (lam (i : Integer) t)) | ||
(build n (lam (i : Integer) f))) | ||
(build 0 (lam (i : Integer) (if p t f)))))""", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
n
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, and comments added inside rule.
I've not added the Tuple Integer Integer variant due to complexities in the comparison gt n 0
. This needs either
- plumbing in a symtab (from prelude.ks?) with a
mul (Tuple Integer Integer)
to use in a condition like(gt (mul (get$1$2 n) (get$2$2 n)) 0)
. I was puzzled how the 1d rule was working without a symtab, but I findgt
is defined as primitive:knossos-ksc/src/python/ksc/prim.py
Lines 126 to 128 in 11155b4
if n == 2 and name in ("eq", "ne", "lt", "gt", "lte", "gte"): assert tys[0] == tys[1] # TODO: MOVEEQ Move eq to prelude return Type.Bool - If the rule uses the prelude then does it really belong in rewrites_ast.py rather than rewrites_prelude.py? (Or rewrites_prim.py, as
build
is primitive rather than AST!)
- If the rule uses the prelude then does it really belong in rewrites_ast.py rather than rewrites_prelude.py? (Or rewrites_prim.py, as
- adding "product" to prim.py (result type presumably either float or int depending on the argument)
- some rather grotesque condition like
if (if (gt (get$1$2 n) 0) (gt (get$2$2 n) 0) False) ....
as a way of "and"ing two bools
Let's move to #972; it seems possible to do this without sum-types, and (according to taste) even without new expressions. |
Do we want to abandon this in favour of an "if-with-evidence" approach (some discussion in #972), merge it, or something else? I think if-with-evidence allows us to lift some functions out of ifs, if we have the evidence. But if we can't do that, the conservative position has to be not to allow lifting out of if, as here? |
I think it's fine to merge the safe rules here, and then add the evidence-based ones when decided. |
This addresses AB#19240, adding a bunch of rules for lifting let(-binds) and if(-conditions) and binds, roughly of the form
These are quite complex rules with numerous side conditions, for example let-variable escape (
(let (x e1) (let (y e2) body))
==>(let (y e2) (let (x e1) body))
only ifx
is not free ine2
).In particular we allow lifting a computation out of one arm of an If only when we know that evaluating the computation before/without testing the if condition, cannot raise an exception (that would change program behaviour). For now an extremely conservative check is used, and this contrasts with RLO which allowed lifting any computation, for example RLO would have allowed:
This raises some subtle cases, which I've tried to handle via extra rules "lift_let_over_if_both", "lift_let_over_if_true_dominated", and "lift_let_over_if_false_dominated", but this is only partially successful: there are still cases where RLO would have lifted an expression but ksc will not, where the lifting is safe (regardless of the expression) because the expression is guaranteed to execute anyway. Specifically:
The ksc rewrites here will only allow lifting in the case where the
......
is empty, but this is safe even when ..... is non-empty. As part of One Theorem Prover, some kind of "let-pushing-down" (let-dropping?) rule will be needed here, not in this PR.