-
Notifications
You must be signed in to change notification settings - Fork 146
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
Need rewrite rules for new base conversion #833
Comments
As long as you make the ranges literals, then it's fine. This looks like writing I think the main question for me is "how do you recognize which |
It might work to just have it trigger when the carry range is looser than |
Btw, I think the rule you want is something like (untested) (forall rv rs s rc r0 rx x ry y,
(* some condition about when to trigger the rule -> *)
s ∈ rs -> 0 ∈ r0 -> (n rx + n ry <= r[0~>s-1])%zrange
-> (subst_let rxy := (rx + ry)%zrange in
cstZZ rv rc (Z.add_with_get_carry_full (cstZ rs ('s)) (cstZ r0 ('0)) (cstZ rx x) (cstZ ry y))
= (dlet xy := cstZ rxy (cstZ rx x + cstZ ry y) in
cstZZ rv rc (cstZ rv (Z.shiftr (cstZ rxy xy) (cstZ r[8~>8] ('s))),
(cstZ rc (Z.land (cstZ rxy xy) (cstZ r[2^s-1~>2^s-1] ('(2^s-1))%Z)))))))%Z%zrange. |
I think I've almost got it on the branch now -- to_bytes works, but from_bytes gives me a single None bound on the output that I don't understand. The printed expression tree shows casts in front of all the outputs. If you have time, Jason, I'd appreciate you helping me debug this -- the build from Rules.v to something I can test takes 20 minutes on my machine and uses so much memory that I can't run a browser at the same time, so it's very painful to test things out if I don't know exactly what's wrong. |
I'd like to register a more general concern with rewrite rules being opaque and really difficult to debug and iterate on. The fact that I needed to make the casts literals was something that I wouldn't have discovered without asking, which is a concern given the fact that you're about to leave. Is there a place where acceptable expressions and general guidelines for rewrite rules are codified? My other concern is the slow iteration process. I've spent about five hours today repeatedly running this rewriter build to debug issues with the new rule (the missing bound, the rules firing mistakenly on Fancy code), where the only way for me to tell the effect of my change is to make the change, wait 20 minutes (or more -- at one point I put in a range1 <> range2 precondition and the build expressed dissatisfaction by just hanging until I finally cancelled it) and then see what output is produced. Is it possible to create a piece of code such that, given a particular rewrite rule and an expression, can tell me whether the rule fires on that expression and maybe show me the output without compiling the entire rewriter? That would certainly make this process smoother. |
This should help with #833 (comment)
My first guess is that you forgot a cast somewhere in the output of the rewrite rule? E.g., you write
Sure. When? Or where's a pointer to the branch / bad output?
You actually don't need to worry about this in the
I think a decent amount of this is scattered throughout the rewriter paper, and some partial work on this that I started in https://github.com/mit-plv/rewriter/blob/master/src/Rewriter/Demo.v. I've created mit-plv/rewriter#14 for this, I'll plan to go flesh it out a bit more shortly.
I've just pushed a70c8ea, which adds a couple of test rules at fiat-crypto/src/Rewriter/TestRules.v Lines 48 to 60 in a70c8ea
fiat-crypto/src/Rewriter/Passes/Test.v Lines 43 to 54 in a70c8ea
Passes/Test.v or else Require Import it in BoundsPipeline to interleave it with other passes to test it on the full pipeline without needing to recompile all the other rewriter passes.
|
Thanks for making the test rules! That will be helpful. I'll try again tomorrow (it's late in the UK now). I think it's definitely worthwhile to make some systematic documentation for the rewriter input language, especially if you're aiming for wider adoption -- I'll happily volunteer to review it! I am not sure if anyone other than me and you is using it right now, and if I'm struggling even with the ability to go to you for answers then a lack of documentation could really deter people from trying it out. The branch I've been working on is at https://github.com/mit-plv/fiat-crypto/tree/columns-base-conversion |
I don't think the rewriter is suitable for wider adoption. It's very, very, very much a research prototype, and I don't see it becoming not a research prototype without going through a complete rewriting. Almost all of it is either a pile of hacks or proofs which are made very painful by inline CPS and dependent types. It's going to be hard to maintain, too, in it's current form. I think the ideas are good, but the implementation is just good enough to work for fiat-crypto. (Recall that the prior state-of-the-art was writing the pattern matches on PHOASTs by hand.) An incomplete list of things that I think need to be rewritten from scratch:
|
(Please respond to the above points in mit-plv/rewriter#15 , so that we don't lose responses.) |
I'll take a look and try it out when I get a chance, hopefully later tonight. |
I can give you an |
You have
First: you don't need to wrap the ranges in literals, because Second: my guess is that the issue is that you're missing the |
Btw, the other thing you can do to make the build faster is change the second boolean argument from
This tells the rewriter to skip the early reduction, which makes the passes significantly faster to create (though makes running the pipeline itself somewhat slower (perhaps 2x slower?)) |
Okay, I've found the root of the issue. The issue is three-fold, sort-of:
We could solve this with either (2) change the argument to It seems like (3) is a good thing to do in general, so I'll make an issue for it. This would consist of changing the bounds on fiat-crypto/src/Util/ZRange/Operations.v Line 157 in dc55596
to something like := four_corners_and_zero (fun x y => if x <? 0 && y <? 0 then Z.min x y else Z.max x y)
(extend_land_lor_bounds x) (extend_land_lor_bounds y). (the logic is that if I'm currently checking whether or not changing the boolean value on Here's my debugging code (should possibly make it into `src/SlowPrimeSynthesisExamples.v`)Module debugging_21271_from_bytes.
Import Crypto.PushButtonSynthesis.UnsaturatedSolinas.
Import Stringification.C.
Import Stringification.C.Compilers.
Import Stringification.C.Compilers.ToString.
Section __.
Local Existing Instance C.OutputCAPI.
Local Instance static : static_opt := false.
Local Instance : internal_static_opt := true.
Local Instance : emit_primitives_opt := false.
Local Instance : use_mul_for_cmovznz_opt := false.
Local Instance : widen_carry_opt := false.
Local Instance : widen_bytes_opt := false.
Local Instance : only_signed_opt := false.
Local Instance : no_select_opt := false.
Local Instance : should_split_mul_opt := false.
Local Instance : should_split_multiret_opt := false.
Definition n := 3%nat (*5%nat*).
Definition s := 2^127 (* 255*).
Definition c := [(1, 1(*9*))].
Definition machine_wordsize := 64.
Import IR.Compilers.ToString.
Goal True.
pose (sfrom_bytes n s c machine_wordsize "1271") as v.
cbv [sfrom_bytes] in v.
set (k := from_bytes _ _ _ _) in (value of v).
clear v.
cbv [from_bytes] in k.
cbv [Pipeline.BoundsPipeline] in k.
set (k' := Pipeline.PreBoundsPipeline _ _ _ _ _) in (value of k).
vm_compute in k'.
cbv [Rewriter.Util.LetIn.Let_In] in k.
set (k'' := CheckedPartialEvaluateWithBounds _ _ _ _ _ _) in (value of k).
vm_compute in k''.
lazymatch (eval cbv [k''] in k'') with
| @inl ?A ?B ?v => pose v as V; change k'' with (@inl A B V) in (value of k)
end.
cbv beta iota zeta in k.
clear k''.
set (e := GeneralizeVar.FromFlat _) in (value of k).
vm_compute in e.
set (k'' := CheckedPartialEvaluateWithBounds _ _ _ _ _ _) in (value of k).
cbv [CheckedPartialEvaluateWithBounds] in k''.
clear -k''.
cbv [Rewriter.Util.LetIn.Let_In] in k''.
set (e' := (GeneralizeVar.FromFlat (GeneralizeVar.ToFlat e))) in (value of k'').
vm_compute in e'; clear e; rename e' into e.
set (b := (partial.Extract _ _ _)) in (value of k'').
clear -b.
cbv [partial.Extract partial.ident.extract partial.extract_gen type.app_curried partial.extract'] in b.
subst e.
cbv beta iota zeta in b.
Import Rewriter.Util.LetIn.
cbn [partial.abstract_interp_ident] in b.
cbv [partial.abstract_interp_ident] in b.
cbv [ZRange.ident.option.interp] in b.
cbv [ZRange.ident.option.of_literal] in b.
cbn [ZRange.ident.option.interp_Z_cast option_map] in b.
cbv [partial.abstract_domain ZRange.type.base.option.interp type.interp ZRange.type.base.interp] in b.
cbn [fst snd] in b.
(do 54 (lazymatch (eval cbv [b] in b) with
| dlet x := ?v in _ => let v' := (eval vm_compute in v) in change v with v' in (value of b)
end;
unfold Let_In at 1 in (value of b));
lazymatch (eval cbv [b] in b) with
| dlet x := ?v in _ => let v' := (eval vm_compute in v) in change v with v' in (value of b)
end).
unfold Let_In at 1 in (value of b).
lazymatch (eval cbv [b] in b) with
| context[Crypto.Util.Option.bind ?v _] => let v' := (eval vm_compute in v) in change v with v' in (value of b)
end.
cbn [Crypto.Util.Option.bind] in b.
set (k' := Operations.ZRange.land_bounds _ _) in (value of b).
cbv [Operations.ZRange.land_bounds] in k'.
clear -k'.
Print Operations.ZRange.land_lor_bounds.
vm_compute in k'.
Abort.
End __.
End debugging_21271_from_bytes. |
Okay, so it turns out that we can't just assume that cast truncates in just |
It's now possible to pass in an argument at a higher level specifying whether or not casts are assumed to truncate. This should help with mit-plv#833 (comment)
This should fix the issue with `None` in the output bounds in mit-plv#833
Fixing the bounds on I think I'm going to do some exploration on option 2, parameterizing |
In the However, |
It's now possible to pass in an argument at a higher level specifying whether or not casts are assumed to truncate. This should help with #833 (comment)
This should fix the issue with `None` in the output bounds in mit-plv#833
Solved by #838 |
As discussed in a couple of recent fiat-crypto meetings and #813 (also related to #809), I've been trying to clean up
Core.v
by removing the enormousnth_default
proofs about carry functions. As it stands, the only one being used isnth_default_chained_carries_no_reduce
, which is used in base conversion to prove thatconvert_bases
is equivalent to evaluating the input in the source base system and then evenly partitioning it in the destination base system (convert_bases_partitions
). Therefore, removing the need fornth_default
in that one proof will remove some of the largest and nastiest proofs from the core arithmetic library.To that end, in the
columns-base-conversion
branch, I've written a definition ofconvert_bases
that uses the saturated arithmetic library (Columns.from_associational
andColumns.flatten
) to convert between bases. It seems to work in my preliminary tests and the proofs pass. However, because theColumns
definitions useZ.add_get_carry_full
, the output ends up having add-get-carry statements that split at invalid bit indices, e.g.:Instead, we want an add and then a bitwise div/mod, e.g.:
I think existing rewrite rules will take care of translating a Z div/mod into a shiftr/land, but I don't know how to write a rule that will do the div/mod translation in the with-casts rules. Especially problematic is figuring out what the range of
x + y
should be, because it doesn't appear in the initial expression and I think (correct me if I'm wrong) the rewriter won't allow me to write a range in the new expression as an expression (e.g.ZRange.four_corners rx ry
).I see two main solutions:
Z.add_get_carry_full
when some boolean is set, or parameterize Columns over your desired implementation of div/mod (which we can then plug in asZ.add_get_carry_full
for saturated arithmetic and div/mod for base conversion)I think 1 is preferable if it's possible. @JasonGross , can you write this rewrite rule? If 1 is not possible, I think I know how to do 2, but would like some feedback on whether introducing this extra abstraction to Columns is reasonable (@andres-erbsen ?). I think the answer kind of hinges on how we want to view
Z.add_get_carry_full
. Is it a definition that serves as a marker for places where we definitely want to use the carry flag? Or is it a shorthand for "div/mod, figure out if this should use carries based on bounds"?The text was updated successfully, but these errors were encountered: