From 014d919b7f324e48a12842f25ef7c4e7d0b6134e Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Tue, 16 Nov 2021 14:05:06 -0800 Subject: [PATCH 01/18] whoops, fixed the translation of slices to include their lifetimes --- heapster-saw/src/Verifier/SAW/Heapster/RustTypes.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/RustTypes.hs b/heapster-saw/src/Verifier/SAW/Heapster/RustTypes.hs index 86e944900f..9077856643 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/RustTypes.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/RustTypes.hs @@ -367,7 +367,7 @@ instance RsConvert w (Ty Span) (PermExpr (LLVMShapeType w)) where -- If so, build a "fat pointer" = a pair of a pointer to our array -- shape plus a length value return $ PExpr_ExShape $ nu $ \n -> - PExpr_SeqShape (PExpr_PtrShape rw Nothing $ + PExpr_SeqShape (PExpr_PtrShape rw (Just l) $ PExpr_ArrayShape (PExpr_Var n) stride $ subst1 (PExpr_Var n) mb_sh) (PExpr_FieldShape $ LLVMFieldShape $ ValPerm_Eq $ From 61b9fe5008aded96d10515c6a27ed263cd3df2b3 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Wed, 17 Nov 2021 16:42:03 -0800 Subject: [PATCH 02/18] added clone functions as test cases to rust_data --- heapster-saw/examples/rust_data.bc | Bin 209840 -> 219056 bytes heapster-saw/examples/rust_data.rs | 39 ++++++++++++++++++++++++++++ heapster-saw/examples/rust_data.saw | 18 ++++++++++++- 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/heapster-saw/examples/rust_data.bc b/heapster-saw/examples/rust_data.bc index f1ace2bb16aa9e8ff72e4f40c15a8a53a76f1ca6..1fb41e676c04a65e0c462eedc60abf0baef0b472 100644 GIT binary patch delta 101622 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�{)a%I=4w1!YA#IZX7Ewq zRp2872YjUzyb9yH!GorQee4Iea$kbWE0+b=Vc%&^@fLXC{^$SChd`BxnQnz(VSF99 zDeOeZgh>P2dLXYr#`2PTo)a9T!#=1fTc%FZ+Vx`{3iH z#Mgq;lBVVtFX%P2lntv^~n~o&rxu_QSyGVA_|5&_@!k<_`kS_{}Ssq z@Czk=BX~8&-50U14GH_5;N-s;JlL8zu7A6v#Q%)Nn-C`OKuwVNR`3oO#}?zwgy7h# zwcsr<9tz%E;?IE7nLY-bwv-Z6oUg!JW4s)^mEbT?)e@yW-TyrZ?IfWC1WLFDyuHLt za0>7e_z8j|!Eo@iFy0Q{N8;CjPsaGfC56ug(S&w!`}yE~kUyB|X%XhIF66KroVKRr z(!$nIpwGb_@Y&#aAP(Z3dmQ82Li|B+JQgY`Bo$A?c?#5`QHj5lcR(l;fv`j!!D*>i zfy+zX6TAcXW8m`EoC{99@*2446^ef!I2|}$uP9u8#NTLClE;q-Vb36x;?>_XB;ElB zs>lJKEI1tcH!ktJ{8?~uIZ>cGaGJOE1!t(^#zX{$7eDsx=sl1oF zAXpG`a4_*InyAz9CH~6X4NemlyeBxV&}#5tg^-{R=k*~z4BUo&132Zu*FRa8E{8x* zHb)=~A%XLk!TV!8Xj$RRrwW?kN2IAT{LZ@INs0I<@V?-i!Ko=loEyQZIU~yp&j)0m ze*pp|=nJ8rv%od%v(x2{=e*gfR12Igbx3TQtwgHJ8+#2BU$fK>g^ys z4!tQfn)LCKzg_AzKLh$XTpu3NhnIT&NUw(89U5I}T_yh|rQQtEujc;Og#1^OdiRjN zp6ic?^i8GS7Sg|jP7Cx2{>k${Sn3@m{V3PJ3F%FuUaTkdiYW8P=~U$3A?i&c{Zy{g zkFUz{dq%yRNH@5CN=P3N^)`||jO+B<0c8K`sJEB&NziG12SAtCcS_Vdxfk?VT(1i0 z3!`2Y>C2$g`c8x{ukS-qZz<`I^ZeI`@;@E*9wq%nuHPBb-;8=YNZ-!&heG^XI@I2)umOc{J^@>h$$+TK?&^>_Jn+(}wS*3CJ(=@@q0vGjCCOC^k)>FQbC;qg)| zY)VVdm^mZ%t4^AqB&GkVb>7dBU~@8aF$NH1)++CnhuL*>g1Wl9UxUu{&AUHWRL!b-WdoYOj`-ZQ8nKI9kp~CEcW5 z>Al{d_V=QL)n4Uh*0ro`HmNyDJ)YJy^62dK9;|lt?y6Kzq>q}FXPq=QIAi4ENj;r& zlD<)UBR#vO&dnjMuIWa?NhOlGjGlIF*Vj)f=$7kbGnkoeXRT~9ZB%+|H>;`o{9tuw zSxLf6SF15^WRFPWcB#y?nKN8O%o^!LJe4x_Tso!2HKTr5wc1cI#;@ISW-h47)wyJ+ zxrw-*O=z~2aIKu4sH_=Z#kQIKSGIKxH22&18B(4!b;B`pj)5!0upQfbb){O_GVR!D7u%e5v!22y7nNYuV zm3msIU+&tQ8`M~NxNAMoF5bZgb?DEV)|b$#9lVjxqsV(2ez9}O^8a;iNDp_em)@+J z9vb)5`c==XUmt7tUa?JWe!{q8=-%JdzJamdzaILxL;aRr>bh_fxBYn&A5#}^J)p+w z(ZyR5$JoHXt%K{wc8*;7TYI-W+`HbBT_Qs(!mV3b<84`~4k`~fuQ#=8WKi3m_bx4E zxOvON&0GI;*T~~dd#6U!urpXUYW(}B7^eayWdzLhY7>z*-Ucnw{TObY+$|LsA87dC z*lwW0L;ppOeCS4<;4Yd$S^Osh15DgX14a>*$;!#&(ZdwZ zcJP4+Zzs8@O<|9zXyK|^V!<>k$ZClUn#pE2mhziHfq}D$DT~`~FnL>iWWi3{d;>=B IcnW|12USURrT_o{ delta 94547 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 } +pub fn box_list64_clone<'a>(x:&'a Box) -> Box { + return x.clone(); +} + +pub fn list64_clone<'a>(x:&'a List64) -> List64 { + match &x { + List64::Nil64 => List64::Nil64, + List64::Cons64(h,t) => List64::Cons64(*h,box_list64_clone(t)), + } +} + /* Test if a List64 is empty */ pub fn list64_is_empty (l: &List64) -> bool { match l { @@ -516,3 +527,31 @@ pub fn list20_head<'a> (x:&'a List20>) -> &'a List { List20::List20_19(l,_) => l, } } + +impl Clone for List20 { + fn clone<'a>(&'a self) -> Self { + match &self { + List20::List20Head(b) => List20::List20Head(*b), + List20::List20_0(h,t) => List20::List20_0(*h,t.clone()), + List20::List20_1(h,t) => List20::List20_1(*h,t.clone()), + List20::List20_2(h,t) => List20::List20_2(*h,t.clone()), + List20::List20_3(h,t) => List20::List20_3(*h,t.clone()), + List20::List20_4(h,t) => List20::List20_4(*h,t.clone()), + List20::List20_5(h,t) => List20::List20_5(*h,t.clone()), + List20::List20_6(h,t) => List20::List20_6(*h,t.clone()), + List20::List20_7(h,t) => List20::List20_7(*h,t.clone()), + List20::List20_8(h,t) => List20::List20_8(*h,t.clone()), + List20::List20_9(h,t) => List20::List20_9(*h,t.clone()), + List20::List20_10(h,t) => List20::List20_10(*h,t.clone()), + List20::List20_11(h,t) => List20::List20_11(*h,t.clone()), + List20::List20_12(h,t) => List20::List20_12(*h,t.clone()), + List20::List20_13(h,t) => List20::List20_13(*h,t.clone()), + List20::List20_14(h,t) => List20::List20_14(*h,t.clone()), + List20::List20_15(h,t) => List20::List20_15(*h,t.clone()), + List20::List20_16(h,t) => List20::List20_16(*h,t.clone()), + List20::List20_17(h,t) => List20::List20_17(*h,t.clone()), + List20::List20_18(h,t) => List20::List20_18(*h,t.clone()), + List20::List20_19(h,t) => List20::List20_19(*h,t.clone()), + } + } +} diff --git a/heapster-saw/examples/rust_data.saw b/heapster-saw/examples/rust_data.saw index ccd0407510..97c7b7e322 100644 --- a/heapster-saw/examples/rust_data.saw +++ b/heapster-saw/examples/rust_data.saw @@ -495,6 +495,16 @@ list64_is_empty_sym <- heapster_find_symbol env "15list64_is_empty"; heapster_typecheck_fun_rename env list_is_empty_sym "list64_is_empty" "<'a> fn (l: &'a List64<>) -> bool"; +// box_list64_clone +box_list64_clone_sym <- heapster_find_symbol env "16box_list64_clone"; +heapster_assume_fun_rename_prim env box_list64_clone_sym "box_list64_clone" + "<'a> fn(x:&'a Box) -> Box"; + +// list64_clone +list64_clone_sym <- heapster_find_symbol env "12list64_clone"; +heapster_typecheck_fun_rename env list64_clone_sym "list64_clone" + "<'a> fn (x:&'a List64) -> List64"; + hash_map_insert_gt_to_le_sym <- heapster_find_symbol env "hash_map_insert_gt_to_le"; heapster_typecheck_fun_rename env hash_map_insert_gt_to_le_sym @@ -518,10 +528,16 @@ list10_head_sym <- heapster_find_symbol env "11list10_head"; //heapster_typecheck_fun_rename env list10_head_sym "list10_head" "<'a> fn (x:&'a List10>) -> &'a List"; +list20_u64_clone_sym <- heapster_find_symbol env + "List20$LT$u64$GT$$u20$as$u20$core..clone..Clone$GT$5clone"; +heapster_typecheck_fun_rename env list20_u64_clone_sym "list20_u64_clone" + "<'a> fn (&'a List20) -> List20"; + +/* list20_head_sym <- heapster_find_symbol env "11list20_head"; heapster_typecheck_fun_rename env list20_head_sym "list20_head" "<'a> fn (x:&'a List20>) -> &'a List"; - +*/ /*** *** Export to Coq From e17bcbf0820dbc4972416fc8bef108ad8b3855e0 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Wed, 17 Nov 2021 16:43:38 -0800 Subject: [PATCH 03/18] changed how memblock permisisons with variable shapes are proved, so that they look for permissions on the left that contain the offsets on the right, not just those that precisely line up with them --- .../src/Verifier/SAW/Heapster/Implication.hs | 89 ++++++++++++++----- .../src/Verifier/SAW/Heapster/Permissions.hs | 32 +++++-- 2 files changed, 90 insertions(+), 31 deletions(-) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs index c2c9888ece..6e20bb5b78 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs @@ -6685,18 +6685,19 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps proveVarLLVMBlocks x ps psubst (mb_bp' : mb_bps) --- If z is unset and there is a field permission with the required offset and --- length, set z to a field shape with equality permission to an existential --- variable, which is the most general field permission we can make +-- If the shape of mb_bp is an unset variable z and there is a field permission +-- on the left that contains all the offsets of mb_bp, recursively prove a +-- memblock permission with shape fieldsh(eq(y)) for fresh evar y, which is the +-- most general field permission proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps | [nuMP| PExpr_Var mb_z |] <- mb_sh , Left memb <- mbNameBoundP mb_z , Nothing <- psubstLookup psubst memb , Just off <- partialSubst psubst (fmap llvmBlockOffset mb_bp) , Just len <- partialSubst psubst (fmap llvmBlockLen mb_bp) - , Just i <- findIndex (isLLVMAtomicPermWithOffset off) ps - , Perm_LLVMField (fp :: LLVMFieldPerm w sz) <- ps!!i - , bvEq len (llvmFieldLen fp) = + , Just i <- findIndex (llvmPermContainsOffsetBool off) ps + , (Perm_LLVMField (fp :: LLVMFieldPerm w sz)) <- ps!!i + , bvLeq (bvAdd off len) (bvAdd (llvmFieldOffset fp) (llvmFieldLen fp)) = -- Recursively prove a membblock with shape fieldsh(eq(y)) for fresh evar y let mb_bp' = @@ -6713,38 +6714,62 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps setVarM memb (PExpr_FieldShape $ LLVMFieldShape $ ValPerm_Eq e) --- If z is unset and there is an atomic permission with the required offset and --- length (which is not a field permission, because otherwise the previous case --- would match), set z to the shape of that atomic permission and recurse +-- If the shape of mb_bp is an unset variable z and there is an array permission +-- on the left that contains all the offsets of mb_bp, recursively prove a +-- memblock permission with the corresponding array shape proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps | [nuMP| PExpr_Var mb_z |] <- mb_sh , Left memb <- mbNameBoundP mb_z , Nothing <- psubstLookup psubst memb , Just off <- partialSubst psubst (fmap llvmBlockOffset mb_bp) , Just len <- partialSubst psubst (fmap llvmBlockLen mb_bp) - , Just i <- findIndex (isLLVMAtomicPermWithOffset off) ps - , Just bp_lhs <- llvmAtomicPermToBlock (ps!!i) + , Just i <- findIndex (llvmPermContainsOffsetBool off) ps + , (Perm_LLVMArray ap) <- ps!!i + , Just (LLVMArrayIndex bp_cell (BV.BV 0)) <- matchLLVMArrayIndex ap off + , bvIsZero (bvMod len (llvmArrayStride ap)) + , sh_len <- bvDiv len (llvmArrayStride ap) + , bvLeq (bvAdd bp_cell sh_len) (llvmArrayLen ap) + , sh <- PExpr_ArrayShape sh_len (llvmArrayStride ap) (llvmArrayCellShape ap) = + setVarM memb sh >>> + proveVarLLVMBlocks x ps psubst + (fmap (\bp -> bp { llvmBlockShape = sh }) mb_bp : mb_bps) + + +-- If the shape of mb_bp is an unset variable z and there is a block permission +-- on the left with the required offset and length, set z to the shape of that +-- block permission and recurse. Note that proveVarLLVMBlocks1 removes the case +-- where there is a block permission that overlaps with mb_bp. +proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps + | [nuMP| PExpr_Var mb_z |] <- mb_sh + , Left memb <- mbNameBoundP mb_z + , Nothing <- psubstLookup psubst memb + , Just off <- partialSubst psubst (fmap llvmBlockOffset mb_bp) + , Just len <- partialSubst psubst (fmap llvmBlockLen mb_bp) + , Just i <- findIndex (llvmPermContainsOffsetBool off) ps + , (Perm_LLVMBlock bp_lhs) <- ps!!i + , bvEq off (llvmBlockOffset bp_lhs) , bvEq len (llvmBlockLen bp_lhs) , sh_lhs <- llvmBlockShape bp_lhs = - setVarM memb sh_lhs >>> - let mb_bp' = fmap (\bp -> bp { llvmBlockShape = sh_lhs }) mb_bp in - proveVarLLVMBlocks x ps psubst (mb_bp' : mb_bps) + proveVarLLVMBlocks x ps psubst + (fmap (\bp -> bp { llvmBlockShape = sh_lhs }) mb_bp : mb_bps) --- If z is unset and there is an atomic permission with the required offset (but --- not the required length, because otherwise it would have matched the previous --- case), split our memblock permission into two memblock permissions with --- unknown shapes but where the first has the length of this atomic permission --- (so the previous case will match), and then recurse +-- If z is unset and there is an atomic permission that contains the required +-- offset of mb_bp but is shorter than mb_bp, split mb_bp into two memblock +-- permissions with unknown shapes but where the first has the length of this +-- atomic permission, and then recurse proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps | [nuMP| PExpr_Var mb_z |] <- mb_sh , Left memb <- mbNameBoundP mb_z , Nothing <- psubstLookup psubst memb - , Just off <- partialSubst psubst (fmap llvmBlockOffset mb_bp) - , Just i <- findIndex (isLLVMAtomicPermWithOffset off) ps - , Just len1 <- llvmAtomicPermLen (ps!!i) - , not (bvIsZero len1) = + , Just off <- partialSubst psubst (mbLLVMBlockOffset mb_bp) + , Just len <- partialSubst psubst (mbLLVMBlockLen mb_bp) + , Just i <- findIndex (llvmPermContainsOffsetBool off) ps + , Just off_lhs <- llvmAtomicPermOffset (ps!!i) + , Just len_lhs <- llvmAtomicPermLen (ps!!i) + , len1 <- bvSub len_lhs (bvSub off off_lhs) + , bvLt len1 len = -- Build existential memblock perms with fresh variables for shapes, where -- the first one has the length of the atomic perm we found and the other @@ -6776,6 +6801,24 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps setVarM memb (llvmBlockShape bp) >>> implSwapInsertConjM x (Perm_LLVMBlock bp) ps_ret' 0 +proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps + | [nuMP| PExpr_Var mb_z |] <- mb_sh + , Left memb <- mbNameBoundP mb_z + , Nothing <- psubstLookup psubst memb + , Just off <- partialSubst psubst (mbLLVMBlockOffset mb_bp) + , Just len <- partialSubst psubst (mbLLVMBlockLen mb_bp) + , Just i <- findIndex (llvmPermContainsOffsetBool off) ps + , Just off_lhs <- llvmAtomicPermOffset (ps!!i) + , Just len_lhs <- llvmAtomicPermLen (ps!!i) + , len1 <- bvSub len_lhs (bvSub off off_lhs) = + implTraceM (\i -> pretty "proveVarLLVMBlocks2: len1 =" <+> + permPretty i len1 <+> pretty ", len_lhs =" <+> + permPretty i len_lhs <+> pretty ", len =" <+> + permPretty i len) >>> + mbSubstM (\s -> ValPerm_Conj (map (Perm_LLVMBlock . s) + (mb_bp:mb_bps))) >>>= \mb_bps' -> + implFailVarM "proveVarLLVMBlock" x (ValPerm_Conj ps) mb_bps' + proveVarLLVMBlocks2 x ps _ mb_bp _ mb_bps = mbSubstM (\s -> ValPerm_Conj (map (Perm_LLVMBlock . s) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs index 7f7066474c..933b61ffb3 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs @@ -1376,10 +1376,10 @@ bvSub e1 e2 = bvAdd e1 (bvNegate e2) -- | Integer division on bitvector expressions, truncating any factors @i*x@ -- where @i@ is not a multiple of the divisor to zero -bvDiv :: (1 <= w, KnownNat w) => PermExpr (BVType w) -> Integer -> +bvDiv :: (1 <= w, KnownNat w, Integral a) => PermExpr (BVType w) -> a -> PermExpr (BVType w) bvDiv (bvMatch -> (factors, off)) n = - let n_bv = BV.mkBV knownNat n in + let n_bv = BV.mkBV knownNat (toInteger n) in normalizeBVExpr $ PExpr_BV (mapMaybe (\(BVFactor i x) -> if BV.urem i n_bv == BV.zero knownNat then @@ -1389,10 +1389,10 @@ bvDiv (bvMatch -> (factors, off)) n = -- | Integer Modulus on bitvector expressions, where any factors @i*x@ with @i@ -- not a multiple of the divisor are included in the modulus -bvMod :: (1 <= w, KnownNat w) => PermExpr (BVType w) -> Integer -> +bvMod :: (1 <= w, KnownNat w, Integral a) => PermExpr (BVType w) -> a -> PermExpr (BVType w) bvMod (bvMatch -> (factors, off)) n = - let n_bv = BV.mkBV knownNat n in + let n_bv = BV.mkBV knownNat $ toInteger n in normalizeBVExpr $ PExpr_BV (mapMaybe (\f@(BVFactor i _) -> if BV.urem i n_bv /= BV.zero knownNat @@ -3584,15 +3584,23 @@ atomicPermLifetime (Perm_LLVMArray ap) = Just $ llvmArrayLifetime ap atomicPermLifetime (Perm_LLVMBlock bp) = Just $ llvmBlockLifetime bp atomicPermLifetime _ = Nothing --- | Get the offset of an atomic permission, if it has one +-- | Get the starting offset of an atomic permission, if it has one. This +-- includes array permissions which may have some cells borrowed. llvmAtomicPermOffset :: AtomicPerm (LLVMPointerType w) -> Maybe (PermExpr (BVType w)) -llvmAtomicPermOffset = fmap llvmBlockOffset . llvmAtomicPermToBlock +llvmAtomicPermOffset (Perm_LLVMField fp) = Just $ llvmFieldOffset fp +llvmAtomicPermOffset (Perm_LLVMArray ap) = Just $ llvmArrayOffset ap +llvmAtomicPermOffset (Perm_LLVMBlock bp) = Just $ llvmBlockOffset bp +llvmAtomicPermOffset _ = Nothing --- | Get the length of an atomic permission, if it has one +-- | Get the length of an atomic permission, if it has one. This includes array +-- permissions which may have some cells borrowed. llvmAtomicPermLen :: AtomicPerm (LLVMPointerType w) -> Maybe (PermExpr (BVType w)) -llvmAtomicPermLen = fmap llvmBlockLen . llvmAtomicPermToBlock +llvmAtomicPermLen (Perm_LLVMField fp) = Just $ llvmFieldLen fp +llvmAtomicPermLen (Perm_LLVMArray ap) = Just $ llvmArrayLengthBytes ap +llvmAtomicPermLen (Perm_LLVMBlock bp) = Just $ llvmBlockLen bp +llvmAtomicPermLen _ = Nothing -- | Get the range of offsets of an atomic permission, if it has one. Note that -- arrays with borrows do not have a well-defined range. @@ -4333,6 +4341,14 @@ llvmPermContainsOffset off (Perm_LLVMBlock bp) Just ([prop], bvPropHolds prop) llvmPermContainsOffset _ _ = Nothing +-- | Test if an atomic LLVM permission definitely contains an offset. This is +-- the 'Bool' flag returned by 'llvmPermContainsOffset', or 'False' if that is +-- undefined. +llvmPermContainsOffsetBool :: (1 <= w, KnownNat w) => PermExpr (BVType w) -> + AtomicPerm (LLVMPointerType w) -> Bool +llvmPermContainsOffsetBool off p = + maybe False snd $ llvmPermContainsOffset off p + -- | Test if an atomic LLVM permission contains (in the sense of 'bvPropHolds') -- all offsets in a given range llvmAtomicPermContainsRange :: (1 <= w, KnownNat w) => BVRange w -> From c352d91c9db2f46c96c5f9a1067d8b6f7ffc2239 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Wed, 17 Nov 2021 17:06:04 -0800 Subject: [PATCH 04/18] added mux3_mut_refs_u64 and two functions that call it to rust_lifetimes --- heapster-saw/examples/rust_lifetimes.bc | Bin 4208 -> 5968 bytes heapster-saw/examples/rust_lifetimes.rs | 27 ++++++++++++++ heapster-saw/examples/rust_lifetimes.saw | 44 +++++++++++++++++++---- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/heapster-saw/examples/rust_lifetimes.bc b/heapster-saw/examples/rust_lifetimes.bc index e88c6fcec4ddbae7e4c59df0f8c344a97b10e274..63a4e84544e945ddfc829f089dda82b2207725ad 100644 GIT binary patch delta 3653 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 delta 2063 zcmb_cdr%W+5Z_BK@dC%?5~3a?& z5fyDPO0n80Dl@eY9Xmdzf7l^_B0g&IF{7>4(RO@{R;4~_A5Po-5}3;W-MPK}_P6`( zx4U=W_GR{q+||&?Xh1R`JNagAmg1D6dBI{Tt@(sVwKm@wo!HAnz1lE+c4NA-TdLRy zDIm{NSf=)E$x}#$-X+0_R8ss`%NE>PnOM6fl%r~Qi{6&eB;srVU`Fs=>I2WSe~_RM zR2MqcKmSe?DgcZ9V??r?1Qn1g)Adv-_mJoAIk{@kJ_vC4W$g`7E9dT4<^x^O+nwL& z$UE3s(C5f)>Ye}{{m~Dd+DEds1GEqOxu2XIuCGhnUEm+{+(S2aL)KPHT)$Hr%01%n z7i{sHz}S)k`fIE=o$jC@^_6#dQ%=%$ z1bqQZ@TQ{KQ;0i}-&8siPgMYTuR+7y%D^z(OyOR100>d+XYbpz@ezkGCSh#EvN)||5%V?D)#`oAL! zt9T1L79gNfKsvf+CAJ#kbVY$CP3p8B+Hq&92Ttx}EWL&MmTbDeVeGeGHf8SJTXkm2 zl1(%#>KvoGpyri}H8I0AQwGj?1EmEy#W0YI8)X{;0Vio9k~|FS$Gl~`E&@OzO7cHe zw25g^n|Fz0qR#0z&leJk=NU~S5||&2g+g3hZ*hD=eDC(+Z9C#FUrso1Rh6ZnMh10E zUdqSWzP^dstX(<{y@f7cR$2Lhw^uBBFZqk|F)OU)(fQkrqlDmcX0iRkR)B2X%%{y= z8GfGaR!^4sP)4UzCDSVEw4OC&RQTN@=0X09n>u==MB8c=4*t@3iSDd6b-niH+F+t4 z>;DOp6g;Rgc`&0zhu1;f;c4D`Ry;LZHzQ5mF2(MwDU+ix!8Sg$pE9S=QkmN%GU|;2 z-DRE0JKF*}r4npXTS#bjg$P7m9;pXy|3xyk;LV9n)u+Aqoe+rCvtugNmcc0tUpyVt`l zi6t$`dN!`wdaUH)>RS4kGN;iEYjUWw6&70!<=`)*NpxMU(!pP8NqTl*LjP1$w$$;yQe~IGcoWz3?H+7NkIW49O!vb_m6ir<$sTOU470IG zFfokJCbUwMCl-`>+#+_i)TQQcxN~_dlMKZk^ZY|AnGc85Im%XHIok2pV-;(2eiGv8 z-(pysd;7ZnyFE$IzS+x6S~?o7PlgNHKG9yCd9398%hfO9d-XuIuu}wh0b}o`k2@*x zb1-MsH}YnCaJ)7x>122Y+zG#t}*2XSCYXQYuH2uTSqL&vr0uJVdJ=x@TLK6{D90GkXABdhS0yar6DE& diff --git a/heapster-saw/examples/rust_lifetimes.rs b/heapster-saw/examples/rust_lifetimes.rs index 5d01569fe0..7d50c93eb7 100644 --- a/heapster-saw/examples/rust_lifetimes.rs +++ b/heapster-saw/examples/rust_lifetimes.rs @@ -30,3 +30,30 @@ pub fn use_mux_mut_refs2 <'a,'b> (x1: &'a mut u64, x2: &'b mut u64) -> u64 { *x1 = *x1 + *x2; return *x1; } + +pub fn mux3_mut_refs_u64 <'a> (x1: &'a mut u64, x2: &'a mut u64, + x3: &'a mut u64, i: u64) -> &'a mut u64 { + if i == 0 { + return x1; + } else if i == 1 { + return x2; + } else { + return x3; + } +} + +pub fn use_mux3_mut_refs <'a,'b,'c> (x1: &'a mut u64, x2: &'b mut u64, + x3: &'c mut u64) -> u64 { + let r = mux3_mut_refs_u64 (x1,x2,x3,2); + *r = *r + 1; + *x1 = *x1 + *x2 + *x3; + return *x1; +} + +pub fn use_mux3_mut_refs_onel <'a> (x1: &'a mut u64, x2: &'a mut u64, + x3: &'a mut u64) -> u64 { + let r = mux3_mut_refs_u64 (x1,x2,x3,2); + *r = *r + 1; + *x1 = *x1 + *x2 + *x3; + return *x1; +} diff --git a/heapster-saw/examples/rust_lifetimes.saw b/heapster-saw/examples/rust_lifetimes.saw index 140bcf28f6..572d372e05 100644 --- a/heapster-saw/examples/rust_lifetimes.saw +++ b/heapster-saw/examples/rust_lifetimes.saw @@ -13,6 +13,9 @@ heapster_define_perm env "int1" " " "llvmptr 1" "exists x:bv 1.eq(llvmword(x))"; // Integer shapes heapster_define_llvmshape env "u64" 64 "" "fieldsh(int64<>)"; +// bool type +heapster_define_llvmshape env "bool" 64 "" "fieldsh(1,int1<>)"; + /*** *** Assumed Functions @@ -43,19 +46,46 @@ heapster_assume_fun env "llvm.expect.i1" // mux_mut_refs_u64 mux_mut_refs_u64_sym <- heapster_find_symbol env "16mux_mut_refs_u64"; heapster_typecheck_fun_rename env mux_mut_refs_u64_sym "mux_mut_refs_u64" - "(l:lifetime, l1:lifetime ,l2:lifetime). \ - \ l:lowned(arg0:[l]memblock(R,0,8,u64<>), arg1:[l]memblock(R,0,8,u64<>) -o \ - \ arg0:[l1]memblock(W,0,8,u64<>), arg1:[l2]memblock(W,0,8,u64<>)), \ - \ arg0:[l]memblock(W,0,8,u64<>), arg1:[l]memblock(W,0,8,u64<>), arg2:int1<> -o \ - \ l:lowned (ret:[l]memblock(R,0,8,u64<>) -o \ - \ arg0:[l1]memblock(W,0,8,u64<>), arg1:[l2]memblock(W,0,8,u64<>)), \ - \ ret:[l]memblock(W,0,8,u64<>)"; + "<'a> fn (x1: &'a mut u64, x2: &'a mut u64, b: bool) -> &'a mut u64"; + // "(l:lifetime, l1:lifetime ,l2:lifetime). \ + // \ l:lowned(arg0:[l]memblock(R,0,8,u64<>), arg1:[l]memblock(R,0,8,u64<>) -o \ + // \ arg0:[l1]memblock(W,0,8,u64<>), arg1:[l2]memblock(W,0,8,u64<>)), \ + // \ arg0:[l]memblock(W,0,8,u64<>), arg1:[l]memblock(W,0,8,u64<>), arg2:int1<> -o \ + // \ l:lowned (ret:[l]memblock(R,0,8,u64<>) -o \ + // \ arg0:[l1]memblock(W,0,8,u64<>), arg1:[l2]memblock(W,0,8,u64<>)), \ + // \ ret:[l]memblock(W,0,8,u64<>)"; + +// mux_mut_refs_poly +mux_mut_refs_poly_u64_sym <- heapster_find_symbol env "17mux_mut_refs_poly"; +heapster_typecheck_fun_rename env mux_mut_refs_poly_u64_sym "mux_mut_refs_poly_u64" + "<'a> fn (x1: &'a mut u64, x2: &'a mut u64, b: bool) -> &'a mut u64"; // use_mux_mut_refs use_mux_mut_refs_sym <- heapster_find_symbol env "16use_mux_mut_refs"; heapster_typecheck_fun_rename env use_mux_mut_refs_sym "use_mux_mut_refs" "(). empty -o ret:int64<>"; +// use_mux_mut_refs2 +use_mux_mut_refs2_sym <- heapster_find_symbol env "17use_mux_mut_refs2"; +heapster_typecheck_fun_rename env use_mux_mut_refs2_sym "use_mux_mut_refs2" + "<'a,'b> fn (x1: &'a mut u64, x2: &'b mut u64) -> u64"; + +// mux3_mut_refs_u64 +mux3_mut_refs_u64_sym <- heapster_find_symbol env "17mux3_mut_refs_u64"; +heapster_typecheck_fun_rename env mux3_mut_refs_u64_sym "mux3_mut_refs_u64" + "<'a> fn (x1: &'a mut u64, x2: &'a mut u64, \ + \ x3: &'a mut u64, i: u64) -> &'a mut u64"; + +// use_mux3_mut_refs +use_mux3_mut_refs_sym <- heapster_find_symbol env "17use_mux3_mut_refs"; +heapster_typecheck_fun_rename env use_mux3_mut_refs_sym "use_mux3_mut_refs" + "<'a,'b,'c> fn (x1: &'a mut u64, x2: &'b mut u64, x3: &'c mut u64) -> u64"; + +// use_mux3_mut_refs_onel +use_mux3_mut_refs_onel_sym <- heapster_find_symbol env "22use_mux3_mut_refs_onel"; +heapster_typecheck_fun_rename env use_mux3_mut_refs_onel_sym + "use_mux3_mut_refs_onel" + "<'a> fn (x1: &'a mut u64, x2: &'a mut u64, x3: &'a mut u64) -> u64"; /*** *** Export to Coq From 966d32cd8c86106318b4350af643740acd7dd4f3 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Thu, 18 Nov 2021 10:48:42 -0800 Subject: [PATCH 05/18] performance enhancement: changed a number of fmaps and mbMap2s in Implication.hs to use mbMapCl, to avoid the performance overhead of changing fresh pairs to fresh functions --- .../src/Verifier/SAW/Heapster/Implication.hs | 121 +++++++----------- .../src/Verifier/SAW/Heapster/Permissions.hs | 107 +++++++++++++++- 2 files changed, 152 insertions(+), 76 deletions(-) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs index 6e20bb5b78..28c0b2d917 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs @@ -6358,15 +6358,12 @@ proveVarLLVMBlocks2 x ps psubst mb_bp _ mb_bps -- For an unfoldable named shape, prove its unfolding first and then fold it proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps - | [nuMP| PExpr_NamedShape rw l nmsh args |] <- mb_sh - , [nuMP| TrueRepr |] <- mbMatch $ fmap namedShapeCanUnfoldRepr nmsh - , [nuMP| Just mb_sh' |] <- mbMatch $ (mbMap3 unfoldModalizeNamedShape rw l nmsh - `mbApply` args) = + | [nuMP| PExpr_NamedShape _ _ mb_nmsh _ |] <- mb_sh + , Just mb_bp' <- mbUnfoldModalizeNamedShapeBlock mb_bp = + -- Recurse using the unfolded shape - (if mbLift (fmap namedShapeIsRecursive nmsh) then implSetRecRecurseRightM + (if mbNamedShapeIsRecursive mb_nmsh then implSetRecRecurseRightM else return ()) >>> - let mb_bp' = - mbMap2 (\bp sh' -> (bp { llvmBlockShape = sh' })) mb_bp mb_sh' in proveVarLLVMBlocks x ps psubst (mb_bp' : mb_bps) >>> -- Extract out the block perm we proved @@ -6375,8 +6372,7 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps implSplitSwapConjsM x ps_out 1 >>> -- Fold the named shape - partialSubstForceM (fmap - llvmBlockShape mb_bp) "proveVarLLVMBlocks" >>>= \sh -> + partialSubstForceM (mbLLVMBlockShape mb_bp) "proveVarLLVMBlocks" >>>= \sh -> let bp' = bp { llvmBlockShape = sh } in implIntroLLVMBlockNamed x bp' >>> @@ -6389,9 +6385,9 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps -- on the left with this exact offset, length, and shape, because it would have -- matched some previous case, so try to eliminate a memblock and recurse proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps - | [nuMP| PExpr_NamedShape _ _ nmsh _ |] <- mb_sh - , [nuMP| FalseRepr |] <- mbMatch $ fmap namedShapeCanUnfoldRepr nmsh - , Just off <- partialSubst psubst $ fmap llvmBlockOffset mb_bp + | [nuMP| PExpr_NamedShape _ _ mb_nmsh _ |] <- mb_sh + , FalseRepr <- mbNamedShapeCanUnfoldRepr mb_nmsh + , Just off <- partialSubst psubst $ mbLLVMBlockOffset mb_bp , Just i <- findIndex (\case p@(Perm_LLVMBlock _) -> isJust (llvmPermContainsOffset off p) @@ -6407,8 +6403,9 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps | [nuMP| PExpr_EqShape (PExpr_Var mb_z) |] <- mb_sh , Left memb <- mbNameBoundP mb_z , Just blk <- psubstLookup psubst memb = - proveVarLLVMBlocks x ps psubst - (fmap (\bp -> bp { llvmBlockShape = PExpr_EqShape blk }) mb_bp : mb_bps) + let mb_bp' = fmap (\bp -> bp { llvmBlockShape = + PExpr_EqShape blk }) mb_bp in + proveVarLLVMBlocks x ps psubst (mb_bp' : mb_bps) -- If proving an equality shape eqsh(z) for unset evar z, prove any memblock @@ -6421,8 +6418,9 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps -- Locally bind z_sh for the shape of the memblock perm and recurse let mb_bp' = - mbCombine RL.typeCtxProxies $ flip fmap mb_bp $ \bp -> - nu $ \z_sh -> bp { llvmBlockShape = PExpr_Var z_sh } in + mbCombine RL.typeCtxProxies $ flip mbMapCl mb_bp $ + $(mkClosed [| \bp -> nu $ \z_sh -> + bp { llvmBlockShape = PExpr_Var z_sh } |]) in proveVarLLVMBlocksExt1 x ps psubst mb_bp' mb_bps >>> -- Extract out the block perm we proved @@ -6524,17 +6522,19 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps -- requires the first shape to have a well-defined length proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps | [nuMP| PExpr_SeqShape mb_sh1 _ |] <- mb_sh - , mbLift $ fmap (isJust . llvmShapeLength) mb_sh1 = + , mbLift $ mbMapCl $(mkClosed [| isJust . llvmShapeLength |]) mb_sh1 = -- Add the two shapes to mb_bps and recursively call proveVarLLVMBlocks let mb_bps12 = - fmap (\bp -> - let PExpr_SeqShape sh1 sh2 = llvmBlockShape bp in - let Just len1 = llvmShapeLength sh1 in - [bp { llvmBlockLen = len1, llvmBlockShape = sh1 }, - bp { llvmBlockOffset = bvAdd (llvmBlockOffset bp) len1, - llvmBlockLen = bvSub (llvmBlockLen bp) len1, - llvmBlockShape = sh2 }]) mb_bp in + mbMapCl + $(mkClosed [| \bp -> + let PExpr_SeqShape sh1 sh2 = llvmBlockShape bp in + let Just len1 = llvmShapeLength sh1 in + [bp { llvmBlockLen = len1, llvmBlockShape = sh1 }, + bp { llvmBlockOffset = bvAdd (llvmBlockOffset bp) len1, + llvmBlockLen = bvSub (llvmBlockLen bp) len1, + llvmBlockShape = sh2 }] |]) + mb_bp in proveVarLLVMBlocks x ps psubst (mbList mb_bps12 ++ mb_bps) >>> -- Move the block permissions we proved to the top of the stack @@ -6587,9 +6587,8 @@ proveVarLLVMBlocks2 x ps psubst mb_bp _ mb_bps implPushM x (ValPerm_Conj ps') >>> -- Recursively prove the ith disjunct and all the rest of mb_bps - let mb_sh = mbTaggedUnionNthDisj i mb_tag_u in - proveVarLLVMBlocks x ps' psubst - (mbMap2 (\bp sh -> bp { llvmBlockShape = sh }) mb_bp mb_sh : mb_bps) >>> + proveVarLLVMBlocks x ps' psubst (mbTaggedUnionNthDisjBlock i mb_bp + : mb_bps) >>> -- Move the block permission with shape mb_sh to the top of the stack getTopDistPerm x >>>= \(ValPerm_Conj ps'') -> @@ -6605,7 +6604,7 @@ proveVarLLVMBlocks2 x ps psubst mb_bp _ mb_bps -- If proving a disjunctive shape, try to prove one of the disjuncts proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps - | [nuMP| PExpr_OrShape mb_sh1 mb_sh2 |] <- mb_sh = + | [nuMP| PExpr_OrShape _ _ |] <- mb_sh = -- Build a computation that tries returning True here, and if that fails -- returns False; True is used for sh1 while False is used for sh2 @@ -6613,8 +6612,7 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps (pure True) (pure False) >>>= \is_case1 -> -- Prove the chosen shape by recursively calling proveVarLLVMBlocks - let mb_sh' = if is_case1 then mb_sh1 else mb_sh2 in - let mb_bp' = mbMap2 (\bp sh -> bp { llvmBlockShape = sh }) mb_bp mb_sh' in + let mb_bp' = mbDisjBlockToSubShape is_case1 mb_bp in proveVarLLVMBlocks x ps psubst (mb_bp' : mb_bps) >>> -- Move the block permission we proved to the top of the stack @@ -6637,14 +6635,12 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps -- If proving an existential shape, introduce an evar and recurse proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps - | [nuMP| PExpr_ExShape mb_mb_sh' |] <- mb_sh = + | [nuMP| PExpr_ExShape mb_mb_sh |] <- mb_sh + , (_ :: Mb ctx (Binding a (PermExpr (LLVMShapeType w)))) <- mb_mb_sh + , a <- knownRepr :: TypeRepr a + , mb_bp' <- mbExBlockToSubShape a mb_bp = -- Prove the sub-shape in the context of a new existential variable - let mb_bp' = - mbCombine RL.typeCtxProxies $ - mbMap2 (\bp mb_sh' -> - fmap (\sh -> bp { llvmBlockShape = sh }) mb_sh') - mb_bp mb_mb_sh' in proveVarLLVMBlocksExt1 x ps psubst mb_bp' mb_bps >>>= \e -> -- Move the block permission we proved to the top of the stack @@ -6652,15 +6648,13 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps implSplitSwapConjsM x ps' 1 >>> -- Prove an existential around the memblock permission we proved - partialSubstForceM (mbMap2 (,) - mb_bp mb_mb_sh') "proveVarLLVMBlock" >>>= \(bp,mb_sh') -> - introExistsM x e (fmap (\sh -> ValPerm_LLVMBlock $ - bp { llvmBlockShape = sh }) mb_sh') >>> + partialSubstForceM mb_bp "proveVarLLVMBlock" >>>= \bp_out -> + introExistsM x e (mbValPerm_LLVMBlock $ exBlockToSubShape a bp_out) >>> -- Now coerce the existential permission on top of the stack to a memblock -- perm with existential shape, and move it back into position - implSimplM Proxy (SImpl_IntroLLVMBlockEx x bp) >>> - implSwapInsertConjM x (Perm_LLVMBlock bp) (tail ps') 0 + implSimplM Proxy (SImpl_IntroLLVMBlockEx x bp_out) >>> + implSwapInsertConjM x (Perm_LLVMBlock bp_out) (tail ps') 0 -- If proving an evar shape that has already been set, substitute and recurse @@ -6801,24 +6795,6 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps setVarM memb (llvmBlockShape bp) >>> implSwapInsertConjM x (Perm_LLVMBlock bp) ps_ret' 0 -proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps - | [nuMP| PExpr_Var mb_z |] <- mb_sh - , Left memb <- mbNameBoundP mb_z - , Nothing <- psubstLookup psubst memb - , Just off <- partialSubst psubst (mbLLVMBlockOffset mb_bp) - , Just len <- partialSubst psubst (mbLLVMBlockLen mb_bp) - , Just i <- findIndex (llvmPermContainsOffsetBool off) ps - , Just off_lhs <- llvmAtomicPermOffset (ps!!i) - , Just len_lhs <- llvmAtomicPermLen (ps!!i) - , len1 <- bvSub len_lhs (bvSub off off_lhs) = - implTraceM (\i -> pretty "proveVarLLVMBlocks2: len1 =" <+> - permPretty i len1 <+> pretty ", len_lhs =" <+> - permPretty i len_lhs <+> pretty ", len =" <+> - permPretty i len) >>> - mbSubstM (\s -> ValPerm_Conj (map (Perm_LLVMBlock . s) - (mb_bp:mb_bps))) >>>= \mb_bps' -> - implFailVarM "proveVarLLVMBlock" x (ValPerm_Conj ps) mb_bps' - proveVarLLVMBlocks2 x ps _ mb_bp _ mb_bps = mbSubstM (\s -> ValPerm_Conj (map (Perm_LLVMBlock . s) @@ -6914,6 +6890,8 @@ proveVarImplFoldRight x p mb_p = case mbMatch mb_p of _ -> pure ()) >>> implLookupNamedPerm npn >>>= \np -> implPopM x p >>> + -- FIXME: how to replace this mbMap2 with mbMapCl, to avoid refreshing all + -- the names in mb_args and mb_off? Maybe they aren't that big anyway... proveVarImplInt x (mbMap2 (unfoldPerm np) mb_args mb_off) >>> partialSubstForceM (mbMap2 (,) mb_args mb_off) "proveVarImplFoldRight" >>>= \(args,off) -> @@ -7200,12 +7178,14 @@ proveVarConjImpl x ps mb_ps = rank -> rank) (mbList mb_ps) of Just i -> - let mb_p = fmap (!!i) mb_ps in - let mb_ps' = fmap (deleteNth i) mb_ps in + let mb_p = mbMapCl ($(mkClosed [| flip (!!) |]) + `clApply` toClosed i) mb_ps in + let mb_ps' = mbMapCl ($(mkClosed [| deleteNth |]) + `clApply` toClosed i) mb_ps in proveVarAtomicImpl x ps mb_p >>> - proveVarImplInt x (fmap ValPerm_Conj mb_ps') >>> - (partialSubstForceM (mbMap2 (,) - mb_ps' mb_p) "proveVarConjImpl") >>>= \(ps',p) -> + proveVarImplInt x (mbValPerm_Conj mb_ps') >>> + partialSubstForceM mb_ps' "proveVarConjImpl" >>>= \ps' -> + partialSubstForceM mb_p "proveVarConjImpl" >>>= \p -> implInsertConjM x p ps' i Nothing -> implTraceM @@ -7558,16 +7538,6 @@ type ExDistPerms vars ps = Mb vars (DistPerms ps) distPermsToExDistPerms :: DistPerms ps -> ExDistPerms RNil ps distPermsToExDistPerms = emptyMb --- | Combine a list of names and a sequence of permissions inside a name-binding --- to get an 'ExDistPerms' -mbValuePermsToExDistPerms :: RAssign Name ps -> Mb vars (ValuePerms ps) -> - ExDistPerms vars ps -mbValuePermsToExDistPerms MNil mb_ps = fmap (const DistPermsNil) mb_ps -mbValuePermsToExDistPerms (xs :>: x) mb_ps = - mbMap2 (\ps p -> DistPermsCons ps x p) - (mbValuePermsToExDistPerms xs (fmap RL.tail mb_ps)) - (fmap RL.head mb_ps) - -- | Substitute arguments into a function permission to get the existentially -- quantified input permissions needed on the arguments funPermExDistIns :: FunPerm ghosts args ret -> RAssign Name args -> @@ -7589,6 +7559,7 @@ extExDistPermsSplit :: ExDistPermsSplit vars ps -> Mb vars (ExprVar b) -> Mb vars (ValuePerm b) -> ExDistPermsSplit vars (ps :> b) extExDistPermsSplit (ExDistPermsSplit ps1 mb_x mb_p ps2) y p' = + -- FIXME: how to replace this mbMap2 with an mbMapCl? ExDistPermsSplit ps1 mb_x mb_p $ mbMap2 DistPermsCons ps2 y `mbApply` p' diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs index 933b61ffb3..9ba87dc29f 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs @@ -1602,6 +1602,10 @@ data ValuePerm (a :: CrucibleType) where ValPerm_Conj :: [AtomicPerm a] -> ValuePerm a +-- | The conjunction of a list of atomic permissions in a binding +mbValPerm_Conj :: Mb ctx [AtomicPerm a] -> Mb ctx (ValuePerm a) +mbValPerm_Conj = mbMapCl $(mkClosed [| ValPerm_Conj |]) + -- | The vacuously true permission is the conjunction of 0 atomic permissions pattern ValPerm_True :: ValuePerm a pattern ValPerm_True = ValPerm_Conj [] @@ -2295,6 +2299,11 @@ namedShapeIsRecursive :: NamedShape b args w -> Bool namedShapeIsRecursive (NamedShape _ _ (RecShapeBody _ _ _)) = True namedShapeIsRecursive _ = False +-- | Test if a 'NamedShape' in a binding is recursive +mbNamedShapeIsRecursive :: Mb ctx (NamedShape b args w) -> Bool +mbNamedShapeIsRecursive = + mbLift . mbMapCl $(mkClosed [| namedShapeIsRecursive |]) + -- | Get a 'BoolRepr' for the Boolean flag for whether a named shape can be -- unfolded namedShapeCanUnfoldRepr :: NamedShape b args w -> BoolRepr b @@ -2302,6 +2311,12 @@ namedShapeCanUnfoldRepr (NamedShape _ _ (DefinedShapeBody _)) = TrueRepr namedShapeCanUnfoldRepr (NamedShape _ _ (OpaqueShapeBody _ _)) = FalseRepr namedShapeCanUnfoldRepr (NamedShape _ _ (RecShapeBody _ _ _)) = TrueRepr +-- | Get a 'BoolRepr' for the Boolean flag for whether a named shape in a +-- binding can be unfolded +mbNamedShapeCanUnfoldRepr :: Mb ctx (NamedShape b args w) -> BoolRepr b +mbNamedShapeCanUnfoldRepr = + mbLift . mbMapCl $(mkClosed [| namedShapeCanUnfoldRepr |]) + -- | Whether a 'NamedShape' can be unfolded namedShapeCanUnfold :: NamedShape b args w -> Bool namedShapeCanUnfold = boolVal . namedShapeCanUnfoldRepr @@ -3787,6 +3802,71 @@ unfoldModalizeNamedShape :: KnownNat w => Maybe (PermExpr RWModalityType) -> unfoldModalizeNamedShape rw l nmsh args = modalizeShape rw l $ unfoldNamedShape nmsh args +-- | Unfold the shape of a block permission using 'unfoldModalizeNamedShape' if +-- it has a named shape +unfoldModalizeNamedShapeBlock :: KnownNat w => LLVMBlockPerm w -> + Maybe (LLVMBlockPerm w) +unfoldModalizeNamedShapeBlock bp + | PExpr_NamedShape rw l nmsh args <- llvmBlockShape bp + , TrueRepr <- namedShapeCanUnfoldRepr nmsh + , Just sh' <- unfoldModalizeNamedShape rw l nmsh args = + Just (bp { llvmBlockShape = sh' }) +unfoldModalizeNamedShapeBlock _ = Nothing + +-- | Unfold the shape of a block permission in a binding using +-- 'unfoldModalizeNamedShape' if it has a named shape +mbUnfoldModalizeNamedShapeBlock :: KnownNat w => Mb ctx (LLVMBlockPerm w) -> + Maybe (Mb ctx (LLVMBlockPerm w)) +mbUnfoldModalizeNamedShapeBlock = + mbMaybe . mbMapCl $(mkClosed [| unfoldModalizeNamedShapeBlock |]) + +-- | Change the shape of a disjunctive block to either its left or right +-- disjunct, depending on whether the supplied 'Bool' is 'True' or 'False' +disjBlockToSubShape :: Bool -> LLVMBlockPerm w -> LLVMBlockPerm w +disjBlockToSubShape flag bp + | PExpr_OrShape sh1 sh2 <- llvmBlockShape bp = + bp { llvmBlockShape = if flag then sh1 else sh2 } +disjBlockToSubShape _ _ = error "disjBlockToSubShape" + +-- | Change the shape of a disjunctive block in a binding to either its left or +-- right disjunct, depending on whether the supplied 'Bool' is 'True' or 'False' +mbDisjBlockToSubShape :: Bool -> Mb ctx (LLVMBlockPerm w) -> + Mb ctx (LLVMBlockPerm w) +mbDisjBlockToSubShape flag = + mbMapCl ($(mkClosed [| disjBlockToSubShape |]) `clApply` toClosed flag) + +-- | A block permission in a binding at some unknown type +data SomeBindingLLVMBlockPerm w = + forall a. SomeBindingLLVMBlockPerm (Binding a (LLVMBlockPerm w)) + +-- | Match an existential shape with the given bidning type +matchExShape :: TypeRepr a -> PermExpr (LLVMShapeType w) -> + Maybe (Binding a (PermExpr (LLVMShapeType w))) +matchExShape a (PExpr_ExShape (mb_sh :: Binding b (PermExpr (LLVMShapeType w)))) + | Just Refl <- testEquality a (knownRepr :: TypeRepr b) = + Just mb_sh +matchExShape _ _ = Nothing + +-- | Change the shape of an existential block to the body of its existential +exBlockToSubShape :: TypeRepr a -> LLVMBlockPerm w -> + Binding a (LLVMBlockPerm w) +exBlockToSubShape a bp + | Just mb_sh <- matchExShape a $ llvmBlockShape bp = + -- NOTE: even when exBlockToSubShape is called inside a binding as part of + -- mbExBlockToSubShape, the existential binding will probably be a fresh + -- function instead of a fresh pair, because it itself has not been + -- mbMatched, so this fmap shouldn't be re-subsituting names + fmap (\sh -> bp { llvmBlockShape = sh }) mb_sh +exBlockToSubShape _ _ = error "exBlockToSubShape" + +-- | Change the shape of an existential block in a binding to the body of its +-- existential +mbExBlockToSubShape :: TypeRepr a -> Mb ctx (LLVMBlockPerm w) -> + Mb (ctx :> a) (LLVMBlockPerm w) +mbExBlockToSubShape a = + mbCombine RL.typeCtxProxies . + mbMapCl ($(mkClosed [| exBlockToSubShape |]) `clApply` toClosed a) + -- | Split a block permission into portions that are before and after a given -- offset, if possible, assuming the offset is in the block permission splitLLVMBlockPerm :: (1 <= w, KnownNat w) => PermExpr (BVType w) -> @@ -3924,6 +4004,29 @@ mbTaggedUnionNthDisj n_top = mbMapCl ($(mkClosed [| \n -> (!!n) . taggedUnionDisjs |]) `clApply` toClosed n_top) +-- | Change a block permisison with a tagged union shape to have the @n@th +-- disjunct shape of this tagged union +taggedUnionNthDisjBlock :: Int -> LLVMBlockPerm w -> LLVMBlockPerm w +taggedUnionNthDisjBlock 0 bp + | PExpr_OrShape sh1 _ <- llvmBlockShape bp = + bp { llvmBlockShape = sh1 } +taggedUnionNthDisjBlock 0 bp = + -- NOTE: this case happens for the last shape in a tagged union, which is not + -- or-ed with anything, and is guaranteed not to be an or itsef (so it won't + -- match the above case) + bp +taggedUnionNthDisjBlock n bp + | PExpr_OrShape _ sh2 <- llvmBlockShape bp = + taggedUnionNthDisjBlock (n-1) $ bp { llvmBlockShape = sh2 } +taggedUnionNthDisjBlock _ _ = error "taggedUnionNthDisjBlock" + +-- | Change the a block permisison in a binding with a tagged union shape to +-- have the @n@th disjunct shape of this tagged union +mbTaggedUnionNthDisjBlock :: Int -> Mb ctx (LLVMBlockPerm w) -> + Mb ctx (LLVMBlockPerm w) +mbTaggedUnionNthDisjBlock n = + mbMapCl ($(mkClosed [| taggedUnionNthDisjBlock |]) `clApply` toClosed n) + -- | Get the tags from a 'TaggedUnionShape' taggedUnionTags :: TaggedUnionShape w sz -> [BV sz] taggedUnionTags (TaggedUnionShape disjs) = map fst $ NonEmpty.toList disjs @@ -3995,7 +4098,8 @@ taggedUnionExTagPerm bp = llvmFieldContents = ValPerm_Eq (PExpr_LLVMWord $ PExpr_Var z) } --- | Convert a tagged union shape in a binding to +-- | Convert a @memblock@ permission in a binding with a tagged union shape to a +-- field permission with permission @eq(z)@ using evar @z@ for the tag mbTaggedUnionExTagPerm :: (1 <= sz, KnownNat sz) => Mb ctx (LLVMBlockPerm w) -> Mb (ctx :> BVType sz) (LLVMFieldPerm w sz) mbTaggedUnionExTagPerm = @@ -6679,6 +6783,7 @@ $(mkNuMatching [t| forall b args a. DefinedPerm b args a |]) $(mkNuMatching [t| forall args a. LifetimeFunctor args a |]) $(mkNuMatching [t| forall ps. LifetimeCurrentPerms ps |]) $(mkNuMatching [t| forall a. SomeLLVMBlockPerm a |]) +$(mkNuMatching [t| forall w. SomeBindingLLVMBlockPerm w |]) instance NuMatchingAny1 LOwnedPerm where nuMatchingAny1Proof = nuMatchingProof From 9fbc4d4afc5361789ab0ce22dc73d2fac250733e Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Thu, 18 Nov 2021 14:50:53 -0800 Subject: [PATCH 06/18] continued changing fmaps and mbMap2s in Implication.hs to use mbMapCl, to avoid the performance overhead of changing fresh pairs to fresh functions --- .../src/Verifier/SAW/Heapster/Implication.hs | 297 +++++++++--------- .../src/Verifier/SAW/Heapster/Permissions.hs | 56 ++++ 2 files changed, 212 insertions(+), 141 deletions(-) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs index 28c0b2d917..d3744e6a1e 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs @@ -4458,7 +4458,8 @@ implElimLLVMBlock x bp@(LLVMBlockPerm { llvmBlockShape = implElimLLVMBlock x bp@(LLVMBlockPerm { llvmBlockShape = PExpr_EqShape (PExpr_Var y) }) = mbVarsM () >>>= \mb_unit -> - withExtVarsM (proveVarImplInt y $ mbCombine RL.typeCtxProxies $ flip fmap mb_unit $ const $ + withExtVarsM (proveVarImplInt y $ mbCombine RL.typeCtxProxies $ + flip mbConst mb_unit $ nu $ \sh -> ValPerm_Conj1 $ Perm_LLVMBlockShape $ PExpr_Var sh) >>>= \(_, sh) -> let bp' = bp { llvmBlockShape = sh } in @@ -4954,21 +4955,23 @@ class ProveEq a where proveEq :: NuMatchingAny1 r => a -> Mb vars a -> ImplM vars s r ps ps (SomeEqProof a) -instance (Eq a, Eq b, ProveEq a, ProveEq b, Substable PermSubst a Identity, +instance (Eq a, Eq b, ProveEq a, ProveEq b, NuMatching a, NuMatching b, + Substable PermSubst a Identity, Substable PermSubst b Identity) => ProveEq (a,b) where proveEq (a,b) mb_ab = - do eqp1 <- proveEq a (fmap fst mb_ab) - eqp2 <- proveEq b (fmap snd mb_ab) + do eqp1 <- proveEq a (mbFst mb_ab) + eqp2 <- proveEq b (mbSnd mb_ab) pure ((,) <$> eqp1 <*> eqp2) instance (Eq a, Eq b, Eq c, ProveEq a, ProveEq b, ProveEq c, + NuMatching a, NuMatching b, NuMatching c, Substable PermSubst a Identity, Substable PermSubst b Identity, Substable PermSubst c Identity) => ProveEq (a,b,c) where proveEq (a,b,c) mb_abc = - do eqp1 <- proveEq a (fmap (\(x,_,_) -> x) mb_abc) - eqp2 <- proveEq b (fmap (\(_,y,_) -> y) mb_abc) - eqp3 <- proveEq c (fmap (\(_,_,z) -> z) mb_abc) + do eqp1 <- proveEq a (mbFst3 mb_abc) + eqp2 <- proveEq b (mbSnd3 mb_abc) + eqp3 <- proveEq c (mbThd3 mb_abc) pure ((,,) <$> eqp1 <*> eqp2 <*> eqp3) instance ProveEq (PermExpr a) where @@ -4987,11 +4990,11 @@ instance ProveEq (LLVMFramePerm w) where instance ProveEq (LLVMBlockPerm w) where proveEq bp mb_bp = - do eqp_rw <- proveEq (llvmBlockRW bp) (fmap llvmBlockRW mb_bp) - eqp_l <- proveEq (llvmBlockLifetime bp) (fmap llvmBlockLifetime mb_bp) - eqp_off <- proveEq (llvmBlockOffset bp) (fmap llvmBlockOffset mb_bp) - eqp_len <- proveEq (llvmBlockLen bp) (fmap llvmBlockLen mb_bp) - eqp_sh <- proveEq (llvmBlockShape bp) (fmap llvmBlockShape mb_bp) + do eqp_rw <- proveEq (llvmBlockRW bp) (mbLLVMBlockRW mb_bp) + eqp_l <- proveEq (llvmBlockLifetime bp) (mbLLVMBlockLifetime mb_bp) + eqp_off <- proveEq (llvmBlockOffset bp) (mbLLVMBlockOffset mb_bp) + eqp_len <- proveEq (llvmBlockLen bp) (mbLLVMBlockLen mb_bp) + eqp_sh <- proveEq (llvmBlockShape bp) (mbLLVMBlockShape mb_bp) pure (LLVMBlockPerm <$> eqp_rw <*> eqp_l <*> eqp_off <*> eqp_len <*> eqp_sh) @@ -5030,7 +5033,7 @@ proveEqH psubst e mb_e = case (e, mbMatch mb_e) of (_, [nuMP| PExpr_Var z |]) | Left memb <- mbNameBoundP z , Just e' <- psubstLookup psubst memb -> - proveEqH psubst e (fmap (const e') z) + proveEqH psubst e (mbConst e' z) -- If the RHS = LHS, do a proof by reflexivity _ | Just e' <- partialSubst psubst mb_e @@ -5049,7 +5052,7 @@ proveEqH psubst e mb_e = case (e, mbMatch mb_e) of pure $ someEqProofTrans (someEqProof1 x e' True) some_eqp (_, ValPerm_Eq e') -> -- If we have y:eq(e'), prove x = e' and apply transitivity - proveEq e (fmap (const e') mb_e) >>= \some_eqp -> + proveEq e (mbConst e' mb_e) >>= \some_eqp -> pure $ someEqProofTrans some_eqp (someEqProof1 y e' False) (_, _) -> -- If we have no equality perms, eliminate perms on x and y to see if we @@ -5087,7 +5090,7 @@ proveEqH psubst e mb_e = case (e, mbMatch mb_e) of | Right x <- mbNameBoundP z -> getVarEqPerm x >>= \case Just e' -> - proveEq e (fmap (const e') mb_e) >>= \eqp -> + proveEq e (mbConst e' mb_e) >>= \eqp -> pure (someEqProofTrans eqp (someEqProof1 x e' False)) Nothing -> proveEqFail e mb_e @@ -5208,7 +5211,7 @@ proveVarLifetimeFunctor' x f args l mb_l psubst = case mbMatch mb_l of [nuMP| PExpr_Var mb_z |] | Left memb <- mbNameBoundP mb_z , Just l2 <- psubstLookup psubst memb -> - proveVarLifetimeFunctor' x f args l (fmap (const l2) mb_z) psubst + proveVarLifetimeFunctor' x f args l (mbConst l2 mb_z) psubst -- If mb_l==l, we are done _ | mbLift $ fmap (== l) mb_l -> @@ -5225,7 +5228,7 @@ proveVarLifetimeFunctor' x f args l mb_l psubst = case mbMatch mb_l of -- specifically if we subsume l1 into l2. ValPerm_LOwned _ _ _ -> let (l',l'_p) = lcurrentPerm l l2 in - proveVarImplInt l' (fmap (const l'_p) mb_z) >>> + proveVarImplInt l' (mbConst l'_p mb_z) >>> getPerm l2 >>>= \case l2_p@(ValPerm_LOwned sub_ls ps_in ps_out) -> implPushM l2 l2_p >>> @@ -5237,7 +5240,7 @@ proveVarLifetimeFunctor' x f args l mb_l psubst = case mbMatch mb_l of -- Otherwise, prove l:[l2]lcurrent and weaken the lifetime _ -> let (l',l'_p) = lcurrentPerm l l2 in - proveVarImplInt l' (fmap (const l'_p) mb_z) >>> + proveVarImplInt l' (mbConst l'_p mb_z) >>> implSimplM Proxy (SImpl_WeakenLifetime x f args l l2) >>> return (PExpr_Var l2) @@ -5490,7 +5493,7 @@ proveVarLLVMFieldH2 x (Perm_LLVMField fp) off mb_fp -- proveVarLifetimeFunctor? let (f, args) = fieldToLTFunc fp'' in proveVarLifetimeFunctor x f args (llvmFieldLifetime fp'') - (fmap llvmFieldLifetime mb_fp) >>>= \l -> + (mbLLVMFieldLifetime mb_fp) >>>= \l -> let fp''' = fp'' { llvmFieldLifetime = l } in -- Step 4: equalize the read/write modalities. This is done after changing @@ -5978,7 +5981,7 @@ proveNamedArg x npn args off memb psubst arg = case mbMatch arg of , LifetimeRepr <- cruCtxLookup (namedPermNameArgs npn) memb , PExpr_Var l2 <- nthPermExpr args memb , l1 /= l2 -> - proveVarImplInt l1 (fmap (const $ ValPerm_LCurrent $ PExpr_Var l2) arg) >>> + proveVarImplInt l1 (mbConst (ValPerm_LCurrent $ PExpr_Var l2) arg) >>> implSimplM Proxy (SImpl_NamedArgCurrent x npn args off memb (PExpr_Var l2)) -- Prove P -o P for any variable rw @@ -6122,7 +6125,7 @@ equalizeBlockModalities x bp mb_bp = let bp' = bp { llvmBlockRW = rw } (f, args) = blockToLTFunc bp' in proveVarLifetimeFunctor x f args (llvmBlockLifetime bp) - (fmap llvmBlockLifetime mb_bp) >>>= \l -> + (mbLLVMBlockLifetime mb_bp) >>>= \l -> return (bp' { llvmBlockLifetime = l }) @@ -6140,14 +6143,14 @@ proveVarLLVMBlocks1 x ps _ [] = -- If the offset, length, and shape of the top block matches one that we already -- have, just cast the rwmodality and lifetime and prove the remaining perms proveVarLLVMBlocks1 x ps psubst (mb_bp:mb_bps) - | Just off <- partialSubst psubst $ fmap llvmBlockOffset mb_bp - , Just len <- partialSubst psubst $ fmap llvmBlockLen mb_bp + | Just off <- partialSubst psubst $ mbLLVMBlockOffset mb_bp + , Just len <- partialSubst psubst $ mbLLVMBlockLen mb_bp + , Just sh <- partialSubst psubst $ mbLLVMBlockShape mb_bp , Just i <- findIndex (\case Perm_LLVMBlock bp -> bvEq (llvmBlockOffset bp) off && bvEq (llvmBlockLen bp) len && - mbLift (fmap ((== llvmBlockShape bp) - . llvmBlockShape) mb_bp) + llvmBlockShape bp == sh _ -> False) ps , Perm_LLVMBlock bp <- ps!!i = @@ -6179,8 +6182,8 @@ proveVarLLVMBlocks1 x ps psubst (mb_bp:mb_bps) -- the left, but the left-hand permission has a defined shape, unfold the -- defined shape proveVarLLVMBlocks1 x ps psubst mb_bps_in@(mb_bp:_) - | Just off <- partialSubst psubst $ fmap llvmBlockOffset mb_bp - , Just len <- partialSubst psubst $ fmap llvmBlockLen mb_bp + | Just off <- partialSubst psubst $ mbLLVMBlockOffset mb_bp + , Just len <- partialSubst psubst $ mbLLVMBlockLen mb_bp , Just i <- findIndex (\case Perm_LLVMBlock bp @@ -6198,8 +6201,8 @@ proveVarLLVMBlocks1 x ps psubst mb_bps_in@(mb_bp:_) -- end, i.e., is of the form sh;emptysh where the natural length of sh is the -- length of the left-hand permission, remove that trailing empty shape proveVarLLVMBlocks1 x ps psubst mb_bps_in@(mb_bp:_) - | Just off <- partialSubst psubst $ fmap llvmBlockOffset mb_bp - , Just len <- partialSubst psubst $ fmap llvmBlockLen mb_bp + | Just off <- partialSubst psubst $ mbLLVMBlockOffset mb_bp + , Just len <- partialSubst psubst $ mbLLVMBlockLen mb_bp , Just i <- findIndex (\case Perm_LLVMBlock bp @@ -6219,8 +6222,8 @@ proveVarLLVMBlocks1 x ps psubst mb_bps_in@(mb_bp:_) -- and/or end of mb_bp. We exclude mb_bp with length 0 as a pathological edge -- case. proveVarLLVMBlocks1 x ps psubst mb_bps_in@(mb_bp:_) - | Just off <- partialSubst psubst $ fmap llvmBlockOffset mb_bp - , Just len <- partialSubst psubst $ fmap llvmBlockLen mb_bp + | Just off <- partialSubst psubst $ mbLLVMBlockOffset mb_bp + , Just len <- partialSubst psubst $ mbLLVMBlockLen mb_bp , rng <- BVRange off len , not (bvIsZero len) , Just i <- findIndex (\case @@ -6280,7 +6283,7 @@ proveVarLLVMBlocks2 :: -- If proving the empty shape for length 0, recursively prove everything else -- and then use the empty introduction rule proveVarLLVMBlocks2 x ps psubst mb_bp [nuMP| PExpr_EmptyShape |] mb_bps - | Just len <- partialSubst psubst $ fmap llvmBlockLen mb_bp + | Just len <- partialSubst psubst $ mbLLVMBlockLen mb_bp , bvIsZero len = -- Do the recursive call without the empty shape and remember what @@ -6305,8 +6308,9 @@ proveVarLLVMBlocks2 x ps psubst mb_bp [nuMP| PExpr_EmptyShape |] mb_bps proveVarLLVMBlocks2 x ps psubst mb_bp [nuMP| PExpr_EmptyShape |] mb_bps = -- Locally bind z_sh for the shape of the memblock perm and recurse let mb_bp' = - mbCombine RL.typeCtxProxies $ flip fmap mb_bp $ \bp -> - nu $ \z_sh -> bp { llvmBlockShape = PExpr_Var z_sh } in + mbCombine RL.typeCtxProxies $ + mbMapCl $(mkClosed [| \bp -> nu $ \z_sh -> + bp { llvmBlockShape = PExpr_Var z_sh } |]) mb_bp in proveVarLLVMBlocksExt1 x ps psubst mb_bp' mb_bps >>> -- Extract out the block perm we proved and coerce it to the empty shape @@ -6325,18 +6329,20 @@ proveVarLLVMBlocks2 x ps psubst mb_bp [nuMP| PExpr_EmptyShape |] mb_bps = -- its shape, prove the memblock with the natural length as well as an -- additional memblock with empty shape and then sequence them together. proveVarLLVMBlocks2 x ps psubst mb_bp _ mb_bps - | Just len <- partialSubst psubst (fmap llvmBlockLen mb_bp) + | Just len <- partialSubst psubst (mbLLVMBlockLen mb_bp) , mbLift $ fmap (maybe False (`bvLt` len) . llvmShapeLength . llvmBlockShape) mb_bp = -- First, build the list of the correctly-sized perm + the empty shape one let mb_bps' = - fmap (\bp -> - let sh_len = fromJust (llvmShapeLength (llvmBlockShape bp)) in - [bp { llvmBlockLen = sh_len }, - bp { llvmBlockOffset = bvAdd (llvmBlockOffset bp) sh_len, - llvmBlockLen = bvSub (llvmBlockLen bp) sh_len, - llvmBlockShape = PExpr_EmptyShape }]) mb_bp in + mbMapCl + $(mkClosed + [| \bp -> + let sh_len = fromJust (llvmShapeLength (llvmBlockShape bp)) in + [bp { llvmBlockLen = sh_len }, + bp { llvmBlockOffset = bvAdd (llvmBlockOffset bp) sh_len, + llvmBlockLen = bvSub (llvmBlockLen bp) sh_len, + llvmBlockShape = PExpr_EmptyShape }] |]) mb_bp in -- Next, do the recursive call proveVarLLVMBlocks x ps psubst (mbList mb_bps' ++ mb_bps) >>> @@ -6445,7 +6451,7 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps | [nuMP| PExpr_EqShape (PExpr_Var mb_z) |] <- mb_sh , Right _ <- mbNameBoundP mb_z - , Just off <- partialSubst psubst $ fmap llvmBlockOffset mb_bp + , Just off <- partialSubst psubst $ mbLLVMBlockOffset mb_bp , Just i <- findIndex (\case p@(Perm_LLVMBlock _) -> isJust (llvmPermContainsOffset off p) @@ -6672,10 +6678,12 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps | [nuMP| PExpr_Var mb_z |] <- mb_sh , Left memb <- mbNameBoundP mb_z , Nothing <- psubstLookup psubst memb - , Just len <- partialSubst psubst (fmap llvmBlockLen mb_bp) + , Just len <- partialSubst psubst (mbLLVMBlockLen mb_bp) , bvIsZero len = setVarM memb PExpr_EmptyShape >>> - let mb_bp' = fmap (\bp -> bp { llvmBlockShape = PExpr_EmptyShape }) mb_bp in + let mb_bp' = mbMapCl $(mkClosed + [| \bp -> bp { llvmBlockShape = + PExpr_EmptyShape } |]) mb_bp in proveVarLLVMBlocks x ps psubst (mb_bp' : mb_bps) @@ -6687,19 +6695,20 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps | [nuMP| PExpr_Var mb_z |] <- mb_sh , Left memb <- mbNameBoundP mb_z , Nothing <- psubstLookup psubst memb - , Just off <- partialSubst psubst (fmap llvmBlockOffset mb_bp) - , Just len <- partialSubst psubst (fmap llvmBlockLen mb_bp) + , Just off <- partialSubst psubst (mbLLVMBlockOffset mb_bp) + , Just len <- partialSubst psubst (mbLLVMBlockLen mb_bp) , Just i <- findIndex (llvmPermContainsOffsetBool off) ps , (Perm_LLVMField (fp :: LLVMFieldPerm w sz)) <- ps!!i , bvLeq (bvAdd off len) (bvAdd (llvmFieldOffset fp) (llvmFieldLen fp)) = -- Recursively prove a membblock with shape fieldsh(eq(y)) for fresh evar y let mb_bp' = - mbCombine RL.typeCtxProxies $ flip fmap mb_bp $ \bp -> - nu $ \(y :: ExprVar (LLVMPointerType sz)) -> - bp { llvmBlockShape = - PExpr_FieldShape $ LLVMFieldShape $ - ValPerm_Eq $ PExpr_Var y } in + mbCombine (MNil :>: (Proxy :: Proxy (LLVMPointerType sz))) $ + mbMapCl $(mkClosed + [| \bp -> nu $ \y -> + bp { llvmBlockShape = + PExpr_FieldShape $ LLVMFieldShape $ + ValPerm_Eq $ PExpr_Var y } |]) mb_bp in proveVarLLVMBlocksExt1 x ps psubst mb_bp' mb_bps >>>= \e -> -- Set z = fieldsh(eq(e)) where e was the value we determined for y; @@ -6715,8 +6724,8 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps | [nuMP| PExpr_Var mb_z |] <- mb_sh , Left memb <- mbNameBoundP mb_z , Nothing <- psubstLookup psubst memb - , Just off <- partialSubst psubst (fmap llvmBlockOffset mb_bp) - , Just len <- partialSubst psubst (fmap llvmBlockLen mb_bp) + , Just off <- partialSubst psubst (mbLLVMBlockOffset mb_bp) + , Just len <- partialSubst psubst (mbLLVMBlockLen mb_bp) , Just i <- findIndex (llvmPermContainsOffsetBool off) ps , (Perm_LLVMArray ap) <- ps!!i , Just (LLVMArrayIndex bp_cell (BV.BV 0)) <- matchLLVMArrayIndex ap off @@ -6737,8 +6746,8 @@ proveVarLLVMBlocks2 x ps psubst mb_bp mb_sh mb_bps | [nuMP| PExpr_Var mb_z |] <- mb_sh , Left memb <- mbNameBoundP mb_z , Nothing <- psubstLookup psubst memb - , Just off <- partialSubst psubst (fmap llvmBlockOffset mb_bp) - , Just len <- partialSubst psubst (fmap llvmBlockLen mb_bp) + , Just off <- partialSubst psubst (mbLLVMBlockOffset mb_bp) + , Just len <- partialSubst psubst (mbLLVMBlockLen mb_bp) , Just i <- findIndex (llvmPermContainsOffsetBool off) ps , (Perm_LLVMBlock bp_lhs) <- ps!!i , bvEq off (llvmBlockOffset bp_lhs) @@ -6893,8 +6902,8 @@ proveVarImplFoldRight x p mb_p = case mbMatch mb_p of -- FIXME: how to replace this mbMap2 with mbMapCl, to avoid refreshing all -- the names in mb_args and mb_off? Maybe they aren't that big anyway... proveVarImplInt x (mbMap2 (unfoldPerm np) mb_args mb_off) >>> - partialSubstForceM (mbMap2 (,) mb_args mb_off) - "proveVarImplFoldRight" >>>= \(args,off) -> + partialSubstForceM mb_args "proveVarImplFoldRight" >>>= \args -> + partialSubstForceM mb_off "proveVarImplFoldRight" >>>= \off -> implFoldNamedM x npn args off _ -> error ("proveVarImplFoldRight: malformed inputs") @@ -6996,8 +7005,8 @@ proveVarAtomicImpl x ps mb_p = case mbMatch mb_p of let n = mbLift mb_n in proveVarImplH x (ValPerm_Conj ps) (mbMap2 (ValPerm_Named n) mb_args mb_off) >>> - partialSubstForceM (mbMap2 (,) - mb_args mb_off) "proveVarAtomicImpl" >>>= \(args,off) -> + partialSubstForceM mb_args "proveVarAtomicImpl" >>>= \args -> + partialSubstForceM mb_off "proveVarAtomicImpl" >>>= \off -> implNamedToConjM x n args off [nuMP| Perm_LLVMFrame mb_fperms |] @@ -7010,7 +7019,7 @@ proveVarAtomicImpl x ps mb_p = case mbMatch mb_p of [nuMP| Perm_LOwned [] _ _ |] | [Perm_LOwned (PExpr_Var l2:_) _ _] <- ps -> implPopM x (ValPerm_Conj ps) >>> implEndLifetimeRecM l2 >>> - proveVarImplInt x (fmap ValPerm_Conj1 mb_p) + proveVarImplInt x (mbValPerm_Conj1 mb_p) [nuMP| Perm_LOwned [] mb_ps_inR mb_ps_outR |] | [Perm_LOwned [] ps_inL ps_outL] <- ps -> @@ -7022,7 +7031,7 @@ proveVarAtomicImpl x ps mb_p = case mbMatch mb_p of -- be in a name-binding for the evars and the LHS needs to not be in a -- name-binding, so ps_inR cannot have any evars. partialSubstForceM mb_ps_inR "proveVarAtomicImpl" >>>= \ps_inR -> - let mb_ps_inL = fmap (const ps_inL) mb_ps_inR in + let mb_ps_inL = mbConst ps_inL mb_ps_inR in solveForPermListImpl ps_inR mb_ps_inL >>>= \(Some neededs1) -> solveForPermListImpl ps_outL mb_ps_outR >>>= \(Some neededs2) -> @@ -7066,7 +7075,7 @@ proveVarAtomicImpl x ps mb_p = case mbMatch mb_p of implSimplM Proxy (SImpl_LCurrentRefl x) [Perm_LCurrent l] | l == l' -> pure () [Perm_LCurrent (PExpr_Var l)] -> - proveVarImplInt l (fmap ValPerm_Conj1 mb_p) >>> + proveVarImplInt l (mbValPerm_Conj1 mb_p) >>> implSimplM Proxy (SImpl_LCurrentTrans x l l') [Perm_LOwned ls ps_in ps_out] | elem l' ls -> implContainedLifetimeCurrentM x ls ps_in ps_out l' @@ -7165,34 +7174,35 @@ proveVarConjImpl x ps (mbMatch -> [nuMP| [] |]) = -- ensuring that we only choose recursive names if there are no defined ones, -- and that, in all cases, we choose a permission that is provable with the -- currently-set evars -proveVarConjImpl x ps mb_ps = - (psubstMbUnsetVars <$> getPSubst) >>>= \mbUnsetVars -> - case findBestIndex - (\mb_ap -> - case isProvablePerm mbUnsetVars (fmap (const x) mb_ap) - (fmap ValPerm_Conj1 mb_ap) of - rank | rank > 0 && mbLift (fmap isDefinedConjPerm mb_ap) -> - isProvablePermMax + 2 - rank | rank > 0 && mbLift (fmap isRecursiveConjPerm mb_ap) -> - isProvablePermMax + 1 - rank -> rank) - (mbList mb_ps) of - Just i -> - let mb_p = mbMapCl ($(mkClosed [| flip (!!) |]) - `clApply` toClosed i) mb_ps in - let mb_ps' = mbMapCl ($(mkClosed [| deleteNth |]) - `clApply` toClosed i) mb_ps in - proveVarAtomicImpl x ps mb_p >>> +proveVarConjImpl x ps_lhs mb_ps = + getPSubst >>>= \psubst -> + case mbMatch $ + mbMapClWithVars + ($(mkClosed + [| \unsetVarsBool ns ps -> + let unsetVars = nameSetFromFlags ns unsetVarsBool in + findBestIndex + (\p -> case isProvablePerm unsetVars Nothing (ValPerm_Conj1 p) of + rank | rank > 0 && isDefinedConjPerm p -> isProvablePermMax + 2 + rank | rank > 0 && isRecursiveConjPerm p -> isProvablePermMax + 1 + rank -> rank) + ps |]) + `clApply` toClosed (psubstUnsetVarsBool psubst)) mb_ps of + [nuMP| Just mb_i |] -> + let i = mbLift mb_i in + let mb_p = mbNth i mb_ps in + let mb_ps' = mbDeleteNth i mb_ps in + proveVarAtomicImpl x ps_lhs mb_p >>> proveVarImplInt x (mbValPerm_Conj mb_ps') >>> partialSubstForceM mb_ps' "proveVarConjImpl" >>>= \ps' -> partialSubstForceM mb_p "proveVarConjImpl" >>>= \p -> implInsertConjM x p ps' i - Nothing -> + [nuMP| Nothing |] -> implTraceM (\i -> sep [PP.fillSep [PP.pretty "Could not determine enough variables to prove permissions:", - permPretty i (fmap ValPerm_Conj mb_ps)]]) >>>= + permPretty i (mbValPerm_Conj mb_ps)]]) >>>= implFailM @@ -7294,7 +7304,7 @@ proveVarImplH x p mb_p = case (p, mbMatch mb_p) of implCatchM "proveVarImplH" (ColonPair x mb_p) -- Reflexivity branch: pop x:P<...>, prove x:eq(e), and use reflexivity - (implPopM x p >>> proveVarImplInt x (fmap ValPerm_Eq mb_e2) >>> + (implPopM x p >>> proveVarImplInt x (mbValPerm_Eq mb_e2) >>> partialSubstForceM mb_args2 "proveVarImpl" >>>= \args2 -> implReachabilityReflM x npn args2 off) @@ -7548,20 +7558,23 @@ funPermExDistIns fun_perm args = -- | A splitting of an existential list of permissions into a prefix, a single -- variable plus permission, and then a suffix -data ExDistPermsSplit vars ps where - ExDistPermsSplit :: ExDistPerms vars ps1 -> - Mb vars (ExprVar a) -> Mb vars (ValuePerm a) -> - ExDistPerms vars ps2 -> - ExDistPermsSplit vars (ps1 :> a :++: ps2) - --- | Extend the @ps@ argument of a 'ExDistPermsSplit' -extExDistPermsSplit :: ExDistPermsSplit vars ps -> - Mb vars (ExprVar b) -> Mb vars (ValuePerm b) -> - ExDistPermsSplit vars (ps :> b) -extExDistPermsSplit (ExDistPermsSplit ps1 mb_x mb_p ps2) y p' = - -- FIXME: how to replace this mbMap2 with an mbMapCl? - ExDistPermsSplit ps1 mb_x mb_p $ - mbMap2 DistPermsCons ps2 y `mbApply` p' +data DistPermsSplit ps where + DistPermsSplit :: RAssign Proxy ps1 -> RAssign Proxy ps2 -> + DistPerms (ps1 :++: ps2) -> + ExprVar a -> ValuePerm a -> + DistPermsSplit (ps1 :> a :++: ps2) + +-- | Make a "base case" 'DistPermsSplit' where the split is at the end +baseDistPermsSplit :: DistPerms ps -> ExprVar a -> ValuePerm a -> + DistPermsSplit (ps :> a) +baseDistPermsSplit ps x p = + DistPermsSplit (RL.map (const Proxy) ps) MNil ps x p + +-- | Extend the @ps@ argument of a 'DistPermsSplit' +extDistPermsSplit :: DistPermsSplit ps -> ExprVar b -> ValuePerm b -> + DistPermsSplit (ps :> b) +extDistPermsSplit (DistPermsSplit prxs1 prxs2 ps12 x p) y p' = + DistPermsSplit prxs1 (prxs2 :>: Proxy) (DistPermsCons ps12 y p') x p -- | The maximum priority returned by 'isProvablePerm' @@ -7572,33 +7585,29 @@ isProvablePermMax = 2 -- given the current set of existential variables whose values have not been -- set. Return a priority for the permission, where higher priorities are proved -- first and 0 means it cannot be proved. -isProvablePerm :: Mb vars (NameSet CrucibleType) -> - Mb vars (ExprVar a) -> Mb vars (ValuePerm a) -> Int +isProvablePerm :: NameSet CrucibleType -> Maybe (ExprVar a) -> + ValuePerm a -> Int -- Lifetime permissions can always be proved, but we want to prove them after -- any other permissions that might depend on them, so they get priority 1 -isProvablePerm _ _ (mbMatch -> [nuMP| ValPerm_Conj mb_ps |]) - | mbLift $ fmap (any (isJust . isLifetimePerm)) mb_ps = 1 +isProvablePerm _ _ (ValPerm_Conj ps) + | any (isJust . isLifetimePerm) ps = 1 -- If x and all the needed vars in p are set, we can prove x:p -isProvablePerm mbUnsetVars mb_x mb_p - | mbNeeded <- mbMap2 (\x p -> NameSet.insert x $ neededVars p) mb_x mb_p - , mbLift $ mbMap2 (\s1 s2 -> - NameSet.null $ - NameSet.intersection s1 s2) mbNeeded mbUnsetVars = 2 +isProvablePerm unsetVars maybe_x p + | neededs <- maybe id (\x -> NameSet.insert x) maybe_x $ neededVars p + , NameSet.null $ NameSet.intersection neededs unsetVars = 2 -- Special case: an LLVMFrame permission can always be proved -isProvablePerm _ _ (mbMatch -> [nuMP| ValPerm_Conj [Perm_LLVMFrame _] |]) = 2 +isProvablePerm _ _ (ValPerm_Conj [Perm_LLVMFrame _]) = 2 -- Special case: a variable permission X can always be proved when the variable -- x and the offset are known, since X is either a free variable, so we can -- substitute the current permissions on x, or X is set to a ground permission, -- so we can definitely try to prove it -isProvablePerm mbUnsetVars mb_x (mbMatch -> [nuMP| ValPerm_Var _ mb_off |]) - | mbNeeded <- mbMap2 (\x off -> NameSet.insert x $ freeVars off) mb_x mb_off - , mbLift $ mbMap2 (\s1 s2 -> - NameSet.null $ - NameSet.intersection s1 s2) mbNeeded mbUnsetVars = 2 +isProvablePerm unsetVars maybe_x (ValPerm_Var _ off) + | neededs <- maybe id (\x -> NameSet.insert x) maybe_x $ freeVars off + , NameSet.null $ NameSet.intersection neededs unsetVars = 2 -- Otherwise we cannot prove the permission, so we return priority 0 isProvablePerm _ _ _ = 0 @@ -7608,24 +7617,20 @@ isProvablePerm _ _ _ = 0 -- one with maximal priority, as returned by 'isProvablePerm', and return its -- location in the supplied list along with its priority. We assume that the -- list is non-empty. -findProvablePerm :: Mb vars (NameSet CrucibleType) -> ExDistPerms vars ps -> - (Int, ExDistPermsSplit vars ps) -findProvablePerm mbUnsetVars mb_ps = case mbMatch mb_ps of - - [nuMP| DistPermsNil |] -> - error "findProvablePerm: empty list" - - [nuMP| DistPermsCons ps_nil@DistPermsNil mb_x mb_p |] -> - (isProvablePerm mbUnsetVars mb_x mb_p, - ExDistPermsSplit ps_nil mb_x mb_p ps_nil) - - [nuMP| DistPermsCons ps mb_x mb_p |] -> - let (best_rank,best) = findProvablePerm mbUnsetVars ps in - let rank = isProvablePerm mbUnsetVars mb_x mb_p in +findProvablePerm :: NameSet CrucibleType -> DistPerms ps -> + (Int, DistPermsSplit ps) +findProvablePerm unsetVars ps = case ps of + DistPermsNil -> error "findProvablePerm: empty list" + DistPermsCons DistPermsNil x p -> + (isProvablePerm unsetVars (Just x) p, + baseDistPermsSplit DistPermsNil x p) + DistPermsCons ps' x p -> + let (best_rank,best) = findProvablePerm unsetVars ps' in + let rank = isProvablePerm unsetVars (Just x) p in if rank > best_rank then - (rank, ExDistPermsSplit ps mb_x mb_p (fmap (const DistPermsNil) mb_p)) + (rank, baseDistPermsSplit ps' x p) else - (best_rank, extExDistPermsSplit best mb_x mb_p) + (best_rank, extDistPermsSplit best x p) -- | Find all existential lifetime variables with @lowned@ permissions in an @@ -7658,21 +7663,29 @@ instantiateLifetimeVars' psubst mb_ps = case mbMatch mb_ps of proveVarsImplAppendInt :: NuMatchingAny1 r => ExDistPerms vars ps -> ImplM vars s r (ps_in :++: ps) ps_in () proveVarsImplAppendInt (mbMatch -> [nuMP| DistPermsNil |]) = return () -proveVarsImplAppendInt ps = +proveVarsImplAppendInt mb_ps = getPSubst >>>= \psubst -> use implStatePerms >>>= \cur_perms -> - case findProvablePerm (psubstMbUnsetVars psubst) ps of - ((> 0) -> True, ExDistPermsSplit ps1 mb_x mb_p ps2) -> - proveExVarImpl psubst mb_x mb_p >>>= \x -> - proveVarsImplAppendInt (mbMap2 appendDistPerms ps1 ps2) >>> - implMoveUpM cur_perms (mbDistPermsToProxies ps1) x (mbDistPermsToProxies ps2) - _ -> - implTraceM - (\i -> - sep [PP.fillSep [PP.pretty - "Could not determine enough variables to prove permissions:", - permPretty i ps]]) >>>= - implFailM + case mbMatch $ + mbMapClWithVars + ($(mkClosed + [| \unsetVarsBool ns ps -> + let unsetVars = nameSetFromFlags ns unsetVarsBool in + findProvablePerm unsetVars ps |]) + `clApply` toClosed (psubstUnsetVarsBool psubst)) mb_ps of + [nuMP| (mb_rank, DistPermsSplit prxs1 prxs2 ps12 mb_x mb_p) |] -> + if mbLift mb_rank > 0 then + proveExVarImpl psubst mb_x mb_p >>>= \x -> + proveVarsImplAppendInt ps12 >>> + implMoveUpM cur_perms (mbLift prxs1) x (mbLift prxs2) + else + implTraceM + (\i -> + sep [PP.fillSep + [PP.pretty + "Could not determine enough variables to prove permissions:", + permPretty i mb_ps]]) >>>= + implFailM -- | Prove a list of existentially-quantified distinguished permissions and put -- those proofs onto the stack. This is the same as 'proveVarsImplAppendInt' @@ -7815,3 +7828,5 @@ proveVarsImplVarEVars mb_ps = proveVarImpl :: NuMatchingAny1 r => ExprVar a -> Mb vars (ValuePerm a) -> ImplM vars s r (ps :> a) ps () proveVarImpl x mb_p = proveVarsImplAppend $ fmap (distPerms1 x) mb_p + +$(mkNuMatching [t| forall ps. DistPermsSplit ps |]) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs index 9ba87dc29f..085eeb8b3e 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs @@ -93,6 +93,33 @@ import Debug.Trace -- * Utility Functions and Definitions ---------------------------------------------------------------------- +-- | Replace the body of a binding with a constant +mbConst :: a -> Mb ctx b -> Mb ctx a +mbConst a = fmap $ const a + +-- | Get the first element of a pair in a binding +mbFst :: NuMatching a => NuMatching b => Mb ctx (a,b) -> Mb ctx a +mbFst = mbMapCl $(mkClosed [| fst |]) + +-- | Get the second element of a pair in a binding +mbSnd :: NuMatching a => NuMatching b => Mb ctx (a,b) -> Mb ctx b +mbSnd = mbMapCl $(mkClosed [| snd |]) + +-- | Get the first element of a triple in a binding +mbFst3 :: NuMatching a => NuMatching b => NuMatching c => + Mb ctx (a,b,c) -> Mb ctx a +mbFst3 = mbMapCl $(mkClosed [| \(a,_,_) -> a |]) + +-- | Get the first element of a triple in a binding +mbSnd3 :: NuMatching a => NuMatching b => NuMatching c => + Mb ctx (a,b,c) -> Mb ctx b +mbSnd3 = mbMapCl $(mkClosed [| \(_,b,_) -> b |]) + +-- | Get the first element of a triple in a binding +mbThd3 :: NuMatching a => NuMatching b => NuMatching c => + Mb ctx (a,b,c) -> Mb ctx c +mbThd3 = mbMapCl $(mkClosed [| \(_,_,c) -> c |]) + -- | FIXME: put this somewhere more appropriate subNat' :: NatRepr m -> NatRepr n -> Maybe (NatRepr (m-n)) subNat' m n @@ -105,6 +132,10 @@ deleteNth :: Int -> [a] -> [a] deleteNth i xs | i >= length xs = error "deleteNth" deleteNth i xs = take i xs ++ drop (i+1) xs +-- | Apply 'deleteNth' inside a name-binding +mbDeleteNth :: NuMatching a => Int -> Mb ctx [a] -> Mb ctx [a] +mbDeleteNth i = mbMapCl ($(mkClosed [| deleteNth |]) `clApply` toClosed i) + -- | Replace the nth element of a list replaceNth :: Int -> a -> [a] -> [a] replaceNth i _ xs | i >= length xs = error "replaceNth" @@ -114,6 +145,9 @@ replaceNth i x xs = take i xs ++ x : drop (i+1) xs insertNth :: Int -> a -> [a] -> [a] insertNth i x xs = take i xs ++ x : drop i xs +-- | Find the @n@th element of a list in a binding +mbNth :: NuMatching a => Int -> Mb ctx [a] -> Mb ctx a +mbNth i = mbMapCl ($(mkClosed [| flip (!!) |]) `clApply` toClosed i) -- | Find all elements of list @l@ where @f@ returns a value and return that -- value plus its index into @l@ @@ -143,6 +177,13 @@ foldr1WithDefault f def (a:as) = f a $ foldr1WithDefault f def as foldMapWithDefault :: (b -> b -> b) -> b -> (a -> b) -> [a] -> b foldMapWithDefault comb def f l = foldr1WithDefault comb def $ map f l +-- | Build a 'NameSet' from a sequence of names and a list of 'Bool' flags +nameSetFromFlags :: RAssign Name (ctx :: RList k) -> [Bool] -> NameSet k +nameSetFromFlags ns flags = + NameSet.fromList $ + mapMaybe (\(n,flag) -> if flag then Just n else Nothing) $ + zip (RL.mapToList SomeName ns) flags + -- | A flag indicating whether an equality test has unfolded a -- recursively-defined name on one side of the equation already data RecurseFlag = RecLeft | RecRight | RecNone @@ -1602,6 +1643,10 @@ data ValuePerm (a :: CrucibleType) where ValPerm_Conj :: [AtomicPerm a] -> ValuePerm a +-- | Build an equality permission in a binding +mbValPerm_Eq :: Mb ctx (PermExpr a) -> Mb ctx (ValuePerm a) +mbValPerm_Eq = mbMapCl $(mkClosed [| ValPerm_Eq |]) + -- | The conjunction of a list of atomic permissions in a binding mbValPerm_Conj :: Mb ctx [AtomicPerm a] -> Mb ctx (ValuePerm a) mbValPerm_Conj = mbMapCl $(mkClosed [| ValPerm_Conj |]) @@ -1787,6 +1832,11 @@ mbLLVMFieldSize _ = knownNat mbLLVMFieldRW :: Mb ctx (LLVMFieldPerm w sz) -> Mb ctx (PermExpr RWModalityType) mbLLVMFieldRW = mbMapCl $(mkClosed [| llvmFieldRW |]) +-- | Get the lifetime-in-binding of a field permission in binding +mbLLVMFieldLifetime :: Mb ctx (LLVMFieldPerm w sz) -> + Mb ctx (PermExpr LifetimeType) +mbLLVMFieldLifetime = mbMapCl $(mkClosed [| llvmFieldLifetime |]) + -- | Get the offset-in-binding of a field permission in binding mbLLVMFieldOffset :: Mb ctx (LLVMFieldPerm w sz) -> Mb ctx (PermExpr (BVType w)) mbLLVMFieldOffset = mbMapCl $(mkClosed [| llvmFieldOffset |]) @@ -5966,6 +6016,12 @@ psubstMbUnsetVars (PartialSubst elems) = then Constant (Just $ SomeName n) else Constant Nothing) ns elems +-- | Return a list of 'Bool's indicating which of the bound names in context +-- @ctx@ are unset in the given partial substitution +psubstUnsetVarsBool :: PartialSubst ctx -> [Bool] +psubstUnsetVarsBool (PartialSubst elems) = + RL.mapToList (\(PSubstElem maybe_e) -> isNothing maybe_e) elems + -- | Set the expression associated with a variable in a partial substitution. It -- is an error if it is already set. psubstSet :: Member ctx a -> PermExpr a -> PartialSubst ctx -> From f8ee7efcd26a9578b3939b231bf382ef93c3f099 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Thu, 18 Nov 2021 17:18:01 -0800 Subject: [PATCH 07/18] added support for proving sub-byte-sized fields using a truncation rule --- .../src/Verifier/SAW/Heapster/Implication.hs | 112 ++++++++++++++++-- .../Verifier/SAW/Heapster/SAWTranslation.hs | 12 ++ 2 files changed, 115 insertions(+), 9 deletions(-) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs index d3744e6a1e..1650c02e1d 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs @@ -625,6 +625,18 @@ data SimplImpl ps_in ps_out where SimplImpl (RNil :> LLVMPointerType w) (RNil :> LLVMPointerType w :> LLVMPointerType w) + -- | Truncate an LLVM field permission that points to a known bitvector: + -- + -- > x:[l]ptr((rw,off) |-> eq(bv1++bv2)) + -- > -o [l]x:ptr((rw,off) |-> eq(bv1)) + -- + -- Note that the definition of @++@ depends on the current endianness. + SImpl_TruncateLLVMWordField :: + (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2) => + ExprVar (LLVMPointerType w) -> + LLVMFieldPerm w sz2 -> BV.BV sz1 -> EndianForm -> + SimplImpl (RNil :> LLVMPointerType w) (RNil :> LLVMPointerType w) + -- | Concatenate two LLVM field permissions that point to known bitvectors: -- -- > [l]x:ptr((rw,off) |-> eq(bv1)) * [l]x:ptr((rw,off+len(bv1)) |-> eq(bv2)) @@ -644,8 +656,6 @@ data SimplImpl ps_in ps_out where -- > x:[l]ptr((rw,off,sz2) |-> true) -- > -o [l]x:ptr((rw,off,sz1) |-> true) -- > * [l]x:ptr((rw,off+sz1,sz2-sz1) |-> true) - -- - -- Note that the definition of @++@ depends on the current endianness. SImpl_SplitLLVMTrueField :: (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2, 1 <= (sz2 - sz1), KnownNat (sz2 - sz1)) => @@ -654,12 +664,20 @@ data SimplImpl ps_in ps_out where SimplImpl (RNil :> LLVMPointerType w) (RNil :> LLVMPointerType w :> LLVMPointerType w) + -- | Truncate an LLVM field permission with @true@ contents: + -- + -- > x:[l]ptr((rw,off,sz2) |-> true) + -- > -o [l]x:ptr((rw,off,sz1) |-> true) + -- + SImpl_TruncateLLVMTrueField :: + (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2) => + ExprVar (LLVMPointerType w) -> LLVMFieldPerm w sz2 -> NatRepr sz1 -> + SimplImpl (RNil :> LLVMPointerType w) (RNil :> LLVMPointerType w) + -- | Concatenate two LLVM field permissions with @true@ contents: -- -- > [l]x:ptr((rw,off,sz1) |-> true) * [l]x:ptr((rw,off+sz1,sz2) |-> true) -- > -o x:[l]ptr((rw,off,sz1+sz2) |-> true) - -- - -- Note that the definition of @++@ depends on the current endianness. SImpl_ConcatLLVMTrueFields :: (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2, 1 <= (sz1 + sz2), KnownNat (sz1 + sz2)) => @@ -1706,6 +1724,13 @@ simplImplIn (SImpl_SplitLLVMWordField x fp bv1 bv2 endianness) = | Just (bv1, bv2) == bvSplit endianness knownNat bv -> distPerms1 x $ ValPerm_LLVMField fp _ -> error "simplImplIn: SImpl_SplitLLVMWordField: malformed input permission" +simplImplIn (SImpl_TruncateLLVMWordField x fp bv1 endianness) = + case llvmFieldContents fp of + ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just bv)) + | Just (bv1', _) <- bvSplit endianness knownNat bv + , bv1 == bv1' -> + distPerms1 x $ ValPerm_LLVMField fp + _ -> error "simplImplIn: SImpl_TruncateLLVMWordField: malformed input permission" simplImplIn (SImpl_ConcatLLVMWordFields x fp1 bv2 _) = case llvmFieldContents fp1 of ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just _)) -> @@ -1718,6 +1743,10 @@ simplImplIn (SImpl_SplitLLVMTrueField x fp _ _) = case llvmFieldContents fp of ValPerm_True -> distPerms1 x $ ValPerm_LLVMField fp _ -> error "simplImplIn: SImpl_SplitLLVMTrueField: malformed field permission" +simplImplIn (SImpl_TruncateLLVMTrueField x fp _) = + case llvmFieldContents fp of + ValPerm_True -> distPerms1 x $ ValPerm_LLVMField fp + _ -> error "simplImplIn: SImpl_TruncateLLVMTrueField: malformed field permission" simplImplIn (SImpl_ConcatLLVMTrueFields x fp1 sz2) = case llvmFieldContents fp1 of ValPerm_True -> @@ -2020,6 +2049,13 @@ simplImplOut (SImpl_SplitLLVMWordField x fp bv1 bv2 endianness) = (llvmFieldSetEqWord fp bv2) (intValue (natRepr bv1) `div` 8))) _ -> error "simplImplOut: SImpl_SplitLLVMWordField: malformed input permission" +simplImplOut (SImpl_TruncateLLVMWordField x fp bv1 endianness) = + case llvmFieldContents fp of + ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just bv)) + | Just (bv1', _) <- bvSplit endianness knownNat bv + , bv1 == bv1' -> + distPerms1 x (ValPerm_LLVMField (llvmFieldSetEqWord fp bv1)) + _ -> error "simplImplOut: SImpl_TruncateLLVMWordField: malformed input permission" simplImplOut (SImpl_ConcatLLVMWordFields x fp1 bv2 endianness) = case llvmFieldContents fp1 of ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just bv1)) -> @@ -2034,6 +2070,12 @@ simplImplOut (SImpl_SplitLLVMTrueField x fp sz1 sz2m1) = llvmFieldAddOffsetInt (llvmFieldSetTrue fp sz2m1) (intValue (natRepr sz1) `div` 8)) _ -> error "simplImplOut: SImpl_SplitLLVMTrueField: malformed field permission" +simplImplOut (SImpl_TruncateLLVMTrueField x fp sz1) = + case llvmFieldContents fp of + ValPerm_True + | intValue sz1 < intValue (llvmFieldSize fp) -> + distPerms1 x (ValPerm_LLVMField $ llvmFieldSetTrue fp sz1) + _ -> error "simplImplOut: SImpl_TruncateLLVMTrueField: malformed field permission" simplImplOut (SImpl_ConcatLLVMTrueFields x fp1 sz2) = case llvmFieldContents fp1 of ValPerm_True -> @@ -2513,12 +2555,18 @@ instance SubstVar PermVarSubst m => [nuMP| SImpl_SplitLLVMWordField x fp bv1 bv2 endianness |] -> SImpl_SplitLLVMWordField <$> genSubst s x <*> genSubst s fp <*> return (mbLift bv1) <*> return (mbLift bv2) <*> return (mbLift endianness) + [nuMP| SImpl_TruncateLLVMWordField x fp bv1 endianness |] -> + SImpl_TruncateLLVMWordField <$> genSubst s x <*> genSubst s fp <*> + return (mbLift bv1) <*> return (mbLift endianness) [nuMP| SImpl_ConcatLLVMWordFields x fp1 bv2 endianness |] -> SImpl_ConcatLLVMWordFields <$> genSubst s x <*> genSubst s fp1 <*> return (mbLift bv2) <*> return (mbLift endianness) [nuMP| SImpl_SplitLLVMTrueField x fp sz1 sz2m1 |] -> SImpl_SplitLLVMTrueField <$> genSubst s x <*> genSubst s fp <*> return (mbLift sz1) <*> return (mbLift sz2m1) + [nuMP| SImpl_TruncateLLVMTrueField x fp sz1 |] -> + SImpl_TruncateLLVMTrueField <$> genSubst s x <*> genSubst s fp <*> + return (mbLift sz1) [nuMP| SImpl_ConcatLLVMTrueFields x fp1 sz2 |] -> SImpl_ConcatLLVMTrueFields <$> genSubst s x <*> genSubst s fp1 <*> return (mbLift sz2) @@ -4184,6 +4232,41 @@ implLLVMFieldSplit x fp sz_bytes implLLVMFieldSplit _ _ _ = implFailMsgM "implLLVMFieldSplit: malformed input permissions" + +-- | Attempt to truncate a pointer permission @ptr((rw,off,sz) |-> p)@ on top of +-- the stack into a permission of the form @ptr((rw,off,sz') |-> p')@ for @sz'@ +-- smaller than @sz@. If @p@ can be coerced to an equality permission +-- @eq(llvmword(bv))@ for a known constant bitvector @bv@, then @p'@ is an +-- equality to the truncation of @bv@; otherwise it is just @true@. +implLLVMFieldTruncate :: + (NuMatchingAny1 r, 1 <= w, KnownNat w, 1 <= sz, KnownNat sz) => + ExprVar (LLVMPointerType w) -> LLVMFieldPerm w sz -> NatRepr sz' -> + ImplM vars s r (ps :> LLVMPointerType w) + (ps :> LLVMPointerType w) + (AtomicPerm (LLVMPointerType w)) +implLLVMFieldTruncate x fp sz' + | Left LeqProof <- decideLeq sz' (llvmFieldSize fp) + , Left LeqProof <- decideLeq (knownNat @1) sz' = + withKnownNat sz' $ + use implStateEndianness >>>= \endianness -> + implLLVMFieldTryProveWordEq x fp >>>= \case + Just bv + | Just (bv', _) <- bvSplit endianness sz' bv -> + implSimplM Proxy (SImpl_TruncateLLVMWordField + x (llvmFieldSetEqWord fp bv) bv' endianness) >>> + return (Perm_LLVMField (llvmFieldSetEqWord fp bv')) + Just _ -> + -- NOTE: this is unreachable because we already know that sz <= + -- llvmFieldSize (because the subNat' above succeeded), so the bvSplit + -- above should always succeed + error "implLLVMFieldTruncate: unreachable case" + Nothing -> + implSimplM Proxy (SImpl_TruncateLLVMTrueField x + (llvmFieldSetTrue fp fp) sz') >>> + return (Perm_LLVMField (llvmFieldSetTrue fp sz')) +implLLVMFieldTruncate _ _ _ = + implFailMsgM "implLLVMFieldTruncate: malformed input permissions" + -- | Concatentate two pointer permissions @ptr((rw,off,sz1) |-> p1)@ and -- @ptr((rw,off+sz1/8,sz2) |-> p2)@ into a single pointer permission of the form -- @ptr((rw,off,sz1+sz2) |-> p)@. If @p1@ and @p2@ are both equality permissions @@ -5512,20 +5595,31 @@ proveVarLLVMFieldH2 x (Perm_LLVMField fp) off mb_fp return () -- If we have a field permission with the correct offset that is bigger than the --- desired field permission, split it and recurse +-- size of the desired field permission rounded up to the nearest byte, split +-- the field permission we have and recurse proveVarLLVMFieldH2 x (Perm_LLVMField fp) off mb_fp | bvEq (llvmFieldOffset fp) off , sz <- mbLLVMFieldSize mb_fp - , intValue sz < intValue (llvmFieldSize fp) - , intValue sz `mod` 8 == 0 - , sz_bytes <- intValue sz `div` 8 = + , sz_bytes <- (intValue sz + 7) `div` 8 + , sz_bytes < llvmFieldSizeBytes fp = implLLVMFieldSplit x fp sz_bytes >>>= \(p1, p2) -> recombinePerm x (ValPerm_Conj1 p2) >>> proveVarLLVMFieldH x p1 off mb_fp +-- If we have a field permission with the correct offset that is bigger than the +-- desired field permission but did not match the previous case, then the +-- desired field is some uneven number of bits smaller than the field we have, +-- so all we can do is truncate the field we have +proveVarLLVMFieldH2 x (Perm_LLVMField fp) off mb_fp + | bvEq (llvmFieldOffset fp) off + , sz <- mbLLVMFieldSize mb_fp + , intValue sz < intValue (llvmFieldSize fp) = + implLLVMFieldTruncate x fp sz >>>= \p -> + proveVarLLVMFieldH x p off mb_fp + -- If we have a field permission with the correct offset that is smaller than -- the desired field permission, split the desired field permission into two, --- recursively xprove the first of these from fp, prove the second with some +-- recursively prove the first of these from fp, prove the second with some -- other permissions, and then concatenate the results proveVarLLVMFieldH2 x (Perm_LLVMField fp) off mb_fp | bvEq (llvmFieldOffset fp) off diff --git a/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs b/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs index 79e2b3aff3..404bb85562 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs @@ -2424,6 +2424,12 @@ translateSimplImpl (ps0 :: Proxy ps0) mb_simpl m = case mbMatch mb_simpl of (\(pctx :>: _) -> RL.append pctx $ typeTransF ttrans []) m + [nuMP| SImpl_TruncateLLVMWordField _ _ _ _ |] -> + do ttrans <- translateSimplImplOut mb_simpl + withPermStackM id + (\(pctx :>: _) -> RL.append pctx $ typeTransF ttrans []) + m + [nuMP| SImpl_ConcatLLVMWordFields _ _ _ _ |] -> do ttrans <- translateSimplImplOut mb_simpl withPermStackM RL.tail @@ -2436,6 +2442,12 @@ translateSimplImpl (ps0 :: Proxy ps0) mb_simpl m = case mbMatch mb_simpl of (\(pctx :>: _) -> RL.append pctx $ typeTransF ttrans []) m + [nuMP| SImpl_TruncateLLVMTrueField _ _ _ |] -> + do ttrans <- translateSimplImplOut mb_simpl + withPermStackM id + (\(pctx :>: _) -> RL.append pctx $ typeTransF ttrans []) + m + [nuMP| SImpl_ConcatLLVMTrueFields _ _ _ |] -> do ttrans <- translateSimplImplOut mb_simpl withPermStackM RL.tail From 3bf1f8098ebbc0593f9f08685b3f98a51e9700b8 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Fri, 19 Nov 2021 06:46:03 -0800 Subject: [PATCH 08/18] whoops, forgot to update the Hobbits dependency! --- cabal.project | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cabal.project b/cabal.project index 54107c9e98..d44dcc3bcf 100644 --- a/cabal.project +++ b/cabal.project @@ -43,4 +43,4 @@ packages: source-repository-package type: git location: https://github.com/eddywestbrook/hobbits.git - tag: 20b6d18758312deaf6a544d474483e537d5f018f + tag: e2df7a85ea8dfebce2be8065afdca96cbaef12ae From 5089d6872e993e63ebe0d03a98046d3618accfe1 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Fri, 19 Nov 2021 11:29:17 -0800 Subject: [PATCH 09/18] switched the pretty-printer to use layoutPretty instead of layoutSmart to avoid exponential behavior; also switched ppCommaSep to use the built-in library function punctuate --- heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs index 085eeb8b3e..4d7c73373c 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs @@ -301,7 +301,7 @@ permPretty :: PermPretty a => PPInfo -> a -> Doc ann permPretty info a = runReader (permPrettyM a) info renderDoc :: Doc ann -> String -renderDoc doc = renderString (layoutSmart opts doc) +renderDoc doc = renderString (layoutPretty opts doc) where opts = LayoutOptions (AvailablePerLine 80 0.8) permPrettyString :: PermPretty a => PPInfo -> a -> String @@ -310,12 +310,10 @@ permPrettyString info a = renderDoc $ permPretty info a tracePretty :: Doc ann -> a -> a tracePretty doc = trace (renderDoc doc) --- | Pretty-print a comma-separated list using 'fillSep' +-- | Pretty-print a comma-separated list ppCommaSep :: [Doc ann] -> Doc ann -ppCommaSep [] = mempty ppCommaSep ds = - PP.group $ align $ fillSep $ map PP.group - (map (<> comma) (take (length ds - 1) ds) ++ [last ds]) + PP.group $ align $ fillSep $ map PP.group $ PP.punctuate comma ds -- | Pretty-print a comma-separated list using 'fillSep' enclosed inside either -- parentheses (if the supplied flag is 'True') or brackets (if it is 'False') From 6afc9174005a301c97ce704fbc28f84eca3c35d8 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Fri, 19 Nov 2021 12:56:26 -0800 Subject: [PATCH 10/18] all bits-to-bytes conversions need to round up! --- .../src/Verifier/SAW/Heapster/Permissions.hs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs index 4d7c73373c..20b9a62bc9 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs @@ -93,6 +93,10 @@ import Debug.Trace -- * Utility Functions and Definitions ---------------------------------------------------------------------- +-- | Take the ceiling of a division +ceil_div :: Integral a => a -> a -> a +ceil_div a b = (a + b - fromInteger 1) `div` b + -- | Replace the body of a binding with a constant mbConst :: a -> Mb ctx b -> Mb ctx a mbConst a = fmap $ const a @@ -694,16 +698,16 @@ shapeLLVMTypeWidth :: KnownNat w => f (LLVMShapeType w) -> NatRepr w shapeLLVMTypeWidth _ = knownNat -- | Convenience function to get the number of bytes = the bit width divided by --- 8 of an LLVM pointer type +-- 8 of an LLVM pointer type rounded up exprLLVMTypeBytes :: KnownNat w => f (LLVMPointerType w) -> Integer -exprLLVMTypeBytes e = intValue (exprLLVMTypeWidth e) `div` 8 +exprLLVMTypeBytes e = intValue (exprLLVMTypeWidth e) `ceil_div` 8 -- | Convenience function to get the number of bytes = the bit width divided by -- 8 of an LLVM pointer type as an expr. Note that this assumes the bit width is -- a multiple of 8, so does not worry about rounding. exprLLVMTypeBytesExpr :: (1 <= w, KnownNat w, 1 <= sz, KnownNat sz) => f (LLVMPointerType sz) -> PermExpr (BVType w) -exprLLVMTypeBytesExpr e = bvInt (intValue (exprLLVMTypeWidth e) `div` 8) +exprLLVMTypeBytesExpr e = bvInt (intValue (exprLLVMTypeWidth e) `ceil_div` 8) -- | Convenience function to get the width of an LLVM pointer type of an -- expression in a binding as an expression @@ -711,7 +715,7 @@ mbExprLLVMTypeBytesExpr :: (1 <= w, KnownNat w, 1 <= sz, KnownNat sz) => Mb ctx (f (LLVMPointerType sz)) -> PermExpr (BVType w) mbExprLLVMTypeBytesExpr mb_e = - bvInt $ div (intValue $ mbLift $ fmap exprLLVMTypeWidth mb_e) 8 + bvInt $ ceil_div (intValue $ mbLift $ fmap exprLLVMTypeWidth mb_e) 8 -- | Pattern-match a permission list expression as a typed list of permissions -- consed onto a terminator, which can either be the empty list (represented by @@ -1820,7 +1824,7 @@ llvmFieldSize _ = knownNat -- | Get the size of an 'LLVMFieldPerm' in bytes llvmFieldSizeBytes :: KnownNat sz => LLVMFieldPerm w sz -> Integer -llvmFieldSizeBytes fp = intValue (llvmFieldSize fp) `div` 8 +llvmFieldSizeBytes fp = intValue (llvmFieldSize fp) `ceil_div` 8 -- | Helper to get a 'NatRepr' for the size of an 'LLVMFieldPerm' in a binding mbLLVMFieldSize :: KnownNat sz => Mb ctx (LLVMFieldPerm w sz) -> NatRepr sz @@ -3702,7 +3706,7 @@ llvmShapeLength (PExpr_NamedShape _ _ nmsh@(NamedShape _ _ llvmShapeLength (unfoldNamedShape nmsh args) llvmShapeLength (PExpr_EqShape _) = Nothing llvmShapeLength (PExpr_PtrShape _ _ sh) - | LLVMShapeRepr w <- exprType sh = Just $ bvInt (intValue w `div` 8) + | LLVMShapeRepr w <- exprType sh = Just $ bvInt (intValue w `ceil_div` 8) | otherwise = Nothing llvmShapeLength (PExpr_FieldShape fsh) = Just $ bvInt $ llvmFieldShapeLength fsh @@ -4232,12 +4236,12 @@ machineWordBytes :: KnownNat w => f w -> Integer machineWordBytes w | natVal w `mod` 8 /= 0 = error "machineWordBytes: word size is not a multiple of 8!" -machineWordBytes w = natVal w `div` 8 +machineWordBytes w = natVal w `ceil_div` 8 -- | Convert bytes to machine words, rounded up, i.e., return @ceil(n/W)@, -- where @W@ is the number of bytes per machine word bytesToMachineWords :: KnownNat w => f w -> Integer -> Integer -bytesToMachineWords w n = (n + machineWordBytes w - 1) `div` machineWordBytes w +bytesToMachineWords w n = n `ceil_div` machineWordBytes w -- | Return the largest multiple of 'machineWordBytes' less than the input prevMachineWord :: KnownNat w => f w -> Integer -> Integer From 1e1208e11d44b34bff617cfd186191fb0735bd0e Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Sun, 21 Nov 2021 14:05:32 -0800 Subject: [PATCH 11/18] changed SImpl_SplitLLVMWordField, SImpl_TruncateLLVMWordField, and SImpl_ConcatLLVMWordFields to PermImpl1 rules that generate fresh variables for the splitting, truncation, and concatenation of bitvector values, respectively, in order to handle symbolic values --- .../src/Verifier/SAW/Heapster/Implication.hs | 365 +++++++++++------- .../src/Verifier/SAW/Heapster/Permissions.hs | 22 +- .../Verifier/SAW/Heapster/SAWTranslation.hs | 122 +++++- 3 files changed, 346 insertions(+), 163 deletions(-) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs index 1650c02e1d..96b75d8ae3 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs @@ -610,47 +610,6 @@ data SimplImpl ps_in ps_out where ExprVar (LLVMPointerType w) -> LLVMFieldPerm w sz -> SimplImpl (RNil :> LLVMPointerType w) (RNil :> LLVMPointerType w) - -- | Split an LLVM field permission that points to a known bitvector: - -- - -- > x:[l]ptr((rw,off) |-> eq(bv1++bv2)) - -- > -o [l]x:ptr((rw,off) |-> eq(bv1)) - -- > * [l]x:ptr((rw,off+len(bv1)) |-> eq(bv2)) - -- - -- Note that the definition of @++@ depends on the current endianness. - SImpl_SplitLLVMWordField :: - (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2, - 1 <= (sz2 - sz1), KnownNat (sz2 - sz1)) => - ExprVar (LLVMPointerType w) -> - LLVMFieldPerm w sz2 -> BV.BV sz1 -> BV.BV (sz2 - sz1) -> EndianForm -> - SimplImpl (RNil :> LLVMPointerType w) - (RNil :> LLVMPointerType w :> LLVMPointerType w) - - -- | Truncate an LLVM field permission that points to a known bitvector: - -- - -- > x:[l]ptr((rw,off) |-> eq(bv1++bv2)) - -- > -o [l]x:ptr((rw,off) |-> eq(bv1)) - -- - -- Note that the definition of @++@ depends on the current endianness. - SImpl_TruncateLLVMWordField :: - (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2) => - ExprVar (LLVMPointerType w) -> - LLVMFieldPerm w sz2 -> BV.BV sz1 -> EndianForm -> - SimplImpl (RNil :> LLVMPointerType w) (RNil :> LLVMPointerType w) - - -- | Concatenate two LLVM field permissions that point to known bitvectors: - -- - -- > [l]x:ptr((rw,off) |-> eq(bv1)) * [l]x:ptr((rw,off+len(bv1)) |-> eq(bv2)) - -- > -o x:[l]ptr((rw,off) |-> eq(bv1++bv2)) - -- - -- Note that the definition of @++@ depends on the current endianness. - SImpl_ConcatLLVMWordFields :: - (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2, - 1 <= (sz1 + sz2), KnownNat (sz1 + sz2)) => - ExprVar (LLVMPointerType w) -> - LLVMFieldPerm w sz1 -> BV.BV sz2 -> EndianForm -> - SimplImpl (RNil :> LLVMPointerType w :> LLVMPointerType w) - (RNil :> LLVMPointerType w) - -- | Split an LLVM field permission with @true@ contents: -- -- > x:[l]ptr((rw,off,sz2) |-> true) @@ -1355,6 +1314,62 @@ data PermImpl1 ps_in ps_outs where (RNil :> '(RNil :> LLVMBlockType w, ps :> LLVMPointerType w :> LLVMBlockType w)) + -- | Split an LLVM field permission that points to a word value, creating + -- fresh variables for the two portions of that word value: + -- + -- > x:[l]ptr((rw,off,sz2) |-> eq(llvmword(e))) + -- > -o y,z.[l]x:ptr((rw,off,sz1) |-> eq(llvmword(y))) + -- > * [l]x:ptr((rw,off+sz1/8,sz2-sz1) |-> eq(llvmword(z))) + -- > * y:p_y * z:p_z + -- + -- If @e@ is a known constant bitvector value @bv1++bv2@, then @p_y@ is + -- @eq(bv1)@ and @p_z@ is @eq(bv2)@, and otherwise these permissions are just + -- @true@. Note that the definition of @++@ depends on the current endianness. + Impl1_SplitLLVMWordField :: + (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2, + 1 <= (sz2 - sz1), KnownNat (sz2 - sz1)) => + ExprVar (LLVMPointerType w) -> LLVMFieldPerm w sz2 -> + NatRepr sz1 -> EndianForm -> + PermImpl1 (ps :> LLVMPointerType w) + (RNil :> '(RNil :> BVType sz1 :> BVType (sz2 - sz1), + ps :> LLVMPointerType w :> LLVMPointerType w :> + BVType sz1 :> BVType (sz2 - sz1))) + + -- | Truncate an LLVM field permission that points to a word value, creating a + -- fresh variable for the remaining portion of the word value: + -- + -- > x:[l]ptr((rw,off,sz2) |-> eq(llvmword(e))) + -- > -o y. [l]x:ptr((rw,off,sz1) |-> eq(llvmword(y))) * y:p_y + -- + -- If @e@ is a known constant bitvector value @bv1++bv2@, then @p_y@ is + -- @eq(bv1)@, and otherwise @p_y@ is just @true@. Note that the definition of + -- @++@ depends on the current endianness. + Impl1_TruncateLLVMWordField :: + (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2) => + ExprVar (LLVMPointerType w) -> LLVMFieldPerm w sz2 -> + NatRepr sz1 -> EndianForm -> + PermImpl1 (ps :> LLVMPointerType w) + (RNil :> '(RNil :> BVType sz1, ps :> LLVMPointerType w :> BVType sz1)) + + -- | Concatenate two LLVM field permissions that point to word values, + -- creating a fresh value for the concatenation of these word values: + -- + -- > [l]x:ptr((rw,off,sz1) |-> eq(llvmword(e1))) + -- > * [l]x:ptr((rw,off+sz1/2,sz2) |-> eq(llvmword(e2))) + -- > -o y. x:[l]ptr((rw,off,sz1+sz2) |-> eq(llvmword(y))) * y:p_y + -- + -- If @e1@ and @e2@ are known constant bitvector values @bv1@ and @bv2@, then + -- @p_y@ is @eq(bv1++bv2)@, and otherwise @p_y@ is just @true@. Note that the + -- definition of @++@ depends on the current endianness. + Impl1_ConcatLLVMWordFields :: + (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2, + 1 <= (sz1 + sz2), KnownNat (sz1 + sz2)) => + ExprVar (LLVMPointerType w) -> LLVMFieldPerm w sz1 -> + PermExpr (BVType sz2) -> EndianForm -> + PermImpl1 (ps :> LLVMPointerType w :> LLVMPointerType w) + (RNil :> '(RNil :> BVType (sz1 + sz2), + ps :> LLVMPointerType w :> BVType (sz1 + sz2))) + -- | Begin a new lifetime: -- -- > . -o ret:lowned(empty -o empty) @@ -1612,6 +1627,15 @@ permImplSucceeds (PermImpl_Step (Impl1_ElimLLVMFieldContents _ _) permImplSucceeds (PermImpl_Step (Impl1_ElimLLVMBlockToEq _ _) (MbPermImpls_Cons _ _ mb_impl)) = mbLift $ fmap permImplSucceeds mb_impl +permImplSucceeds (PermImpl_Step (Impl1_SplitLLVMWordField _ _ _ _) + (MbPermImpls_Cons _ _ mb_impl)) = + mbLift $ fmap permImplSucceeds mb_impl +permImplSucceeds (PermImpl_Step (Impl1_TruncateLLVMWordField _ _ _ _) + (MbPermImpls_Cons _ _ mb_impl)) = + mbLift $ fmap permImplSucceeds mb_impl +permImplSucceeds (PermImpl_Step (Impl1_ConcatLLVMWordFields _ _ _ _) + (MbPermImpls_Cons _ _ mb_impl)) = + mbLift $ fmap permImplSucceeds mb_impl permImplSucceeds (PermImpl_Step Impl1_BeginLifetime (MbPermImpls_Cons _ _ mb_impl)) = mbLift $ fmap permImplSucceeds mb_impl @@ -1718,27 +1742,6 @@ simplImplIn (SImpl_IntroLLVMFieldContents x y fld) = y (llvmFieldContents fld) simplImplIn (SImpl_DemoteLLVMFieldRW x fld) = distPerms1 x (ValPerm_Conj [Perm_LLVMField fld]) -simplImplIn (SImpl_SplitLLVMWordField x fp bv1 bv2 endianness) = - case llvmFieldContents fp of - ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just bv)) - | Just (bv1, bv2) == bvSplit endianness knownNat bv -> - distPerms1 x $ ValPerm_LLVMField fp - _ -> error "simplImplIn: SImpl_SplitLLVMWordField: malformed input permission" -simplImplIn (SImpl_TruncateLLVMWordField x fp bv1 endianness) = - case llvmFieldContents fp of - ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just bv)) - | Just (bv1', _) <- bvSplit endianness knownNat bv - , bv1 == bv1' -> - distPerms1 x $ ValPerm_LLVMField fp - _ -> error "simplImplIn: SImpl_TruncateLLVMWordField: malformed input permission" -simplImplIn (SImpl_ConcatLLVMWordFields x fp1 bv2 _) = - case llvmFieldContents fp1 of - ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just _)) -> - distPerms2 x (ValPerm_LLVMField fp1) x (ValPerm_LLVMField $ - llvmFieldAddOffsetInt - (llvmFieldSetEqWord fp1 bv2) - (intValue (natRepr fp1) `div` 8)) - _ -> error "simplImplIn: SImpl_ConcatLLVMWordFields: malformed input permission" simplImplIn (SImpl_SplitLLVMTrueField x fp _ _) = case llvmFieldContents fp of ValPerm_True -> distPerms1 x $ ValPerm_LLVMField fp @@ -2040,28 +2043,6 @@ simplImplOut (SImpl_IntroLLVMFieldContents x _ fld) = simplImplOut (SImpl_DemoteLLVMFieldRW x fld) = distPerms1 x (ValPerm_Conj [Perm_LLVMField $ fld { llvmFieldRW = PExpr_Read }]) -simplImplOut (SImpl_SplitLLVMWordField x fp bv1 bv2 endianness) = - case llvmFieldContents fp of - ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just bv)) - | bvSplit endianness knownNat bv == Just (bv1, bv2) -> - distPerms2 x (ValPerm_LLVMField (llvmFieldSetEqWord fp bv1)) - x (ValPerm_LLVMField (llvmFieldAddOffsetInt - (llvmFieldSetEqWord fp bv2) - (intValue (natRepr bv1) `div` 8))) - _ -> error "simplImplOut: SImpl_SplitLLVMWordField: malformed input permission" -simplImplOut (SImpl_TruncateLLVMWordField x fp bv1 endianness) = - case llvmFieldContents fp of - ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just bv)) - | Just (bv1', _) <- bvSplit endianness knownNat bv - , bv1 == bv1' -> - distPerms1 x (ValPerm_LLVMField (llvmFieldSetEqWord fp bv1)) - _ -> error "simplImplOut: SImpl_TruncateLLVMWordField: malformed input permission" -simplImplOut (SImpl_ConcatLLVMWordFields x fp1 bv2 endianness) = - case llvmFieldContents fp1 of - ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just bv1)) -> - distPerms1 x (ValPerm_LLVMField $ llvmFieldSetEqWord fp1 $ - bvConcat endianness bv1 bv2) - _ -> error "simplImplIn: SImpl_ConcatLLVMWordFields: malformed input permission" simplImplOut (SImpl_SplitLLVMTrueField x fp sz1 sz2m1) = case llvmFieldContents fp of ValPerm_True -> @@ -2438,7 +2419,40 @@ applyImpl1 _ (Impl1_ElimLLVMBlockToEq x bp) ps = bp { llvmBlockShape = PExpr_EqShape $ PExpr_Var y }) ps) else - error "applyImpl1: SImpl_ElimLLVMBlockToEq: unexpected permission" + error "applyImpl1: Impl1_ElimLLVMBlockToEq: unexpected permission" +applyImpl1 _ (Impl1_SplitLLVMWordField x fp sz1 endianness) ps = + if ps ^. topDistPerm x == ValPerm_LLVMField fp && + intValue sz1 `mod` 8 == 0 then + mbPermSets1 $ nuMultiWithElim1 + (\(_ :>: y :>: z) vps_out -> + flip modifyDistPerms ps $ \(dps :>: _) -> + RL.append dps $ RL.map2 VarAndPerm (MNil :>: x :>: x :>: y :>: z) vps_out) $ + impl1SplitLLVMWordFieldOutPerms fp sz1 endianness + else + error "applyImpl1: Impl1_SplitLLVMWordField: unexpected input permissions" +applyImpl1 _ (Impl1_TruncateLLVMWordField x fp sz1 endianness) ps = + if ps ^. topDistPerm x == ValPerm_LLVMField fp then + mbPermSets1 $ nuWithElim1 + (\y vps_out -> + flip modifyDistPerms ps $ \(dps :>: _) -> + RL.append dps $ RL.map2 VarAndPerm (MNil :>: x :>: y) vps_out) $ + impl1TruncateLLVMWordFieldOutPerms fp sz1 endianness + else + error "applyImpl1: Impl1_TruncateLLVMWordField: unexpected input permissions" +applyImpl1 _ (Impl1_ConcatLLVMWordFields x fp1 e2 endianness) ps = + if ps ^. distPerm (Member_Step Member_Base) x == ValPerm_LLVMField fp1 && + ps ^. topDistPerm x == (ValPerm_LLVMField $ + llvmFieldAddOffsetInt + (llvmFieldSetEqWord fp1 e2) + (intValue (natRepr fp1) `div` 8)) && + intValue (natRepr fp1) `mod` 8 == 0 then + mbPermSets1 $ nuWithElim1 + (\y vps_out -> + flip modifyDistPerms ps $ \(dps :>: _ :>: _) -> + RL.append dps $ RL.map2 VarAndPerm (MNil :>: x :>: y) vps_out) $ + impl1ConcatLLVMWordFieldsOutPerms fp1 e2 endianness + else + error "applyImpl1: Impl1_ConcatLLVMWordField: unexpected input permissions" applyImpl1 _ Impl1_BeginLifetime ps = mbPermSets1 $ nu $ \l -> pushPerm l (ValPerm_LOwned [] MNil MNil) ps applyImpl1 _ (Impl1_TryProveBVProp x prop _) ps = @@ -2446,6 +2460,74 @@ applyImpl1 _ (Impl1_TryProveBVProp x prop _) ps = pushPerm x (ValPerm_Conj [Perm_BVProp prop]) ps +-- | Helper function to compute the output permissions of the +-- 'Impl1_SplitLLVMWordField' rule +impl1SplitLLVMWordFieldOutPerms :: + (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2, + 1 <= (sz2 - sz1), KnownNat (sz2 - sz1)) => + LLVMFieldPerm w sz2 -> NatRepr sz1 -> EndianForm -> + Mb (RNil :> BVType sz1 :> BVType (sz2 - sz1)) + (ValuePerms (RNil :> LLVMPointerType w :> LLVMPointerType w :> + BVType sz1 :> BVType (sz2 - sz1))) +impl1SplitLLVMWordFieldOutPerms fp sz1 endianness = + nuMulti RL.typeCtxProxies $ \(MNil :>: y :>: z) -> + let (p_y,p_z) = + case llvmFieldContents fp of + ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just bv)) + | Just (bv1,bv2) <- bvSplit endianness sz1 bv -> + (ValPerm_Eq (bvBV bv1), ValPerm_Eq (bvBV bv2)) + ValPerm_Eq (PExpr_LLVMWord _) -> + (ValPerm_True, ValPerm_True) + _ -> + error ("applyImpl1: Impl1_SplitLLVMWordField: " + ++ "malformed input permission") in + MNil :>: ValPerm_LLVMField (llvmFieldSetEqWordVar fp y) :>: + ValPerm_LLVMField (llvmFieldAddOffsetInt + (llvmFieldSetEqWordVar fp z) + (intValue sz1 `div` 8)) :>: p_y :>: p_z + +-- | Helper function to compute the output permissions of the +-- 'Impl1_TruncateLLVMWordField' rule +impl1TruncateLLVMWordFieldOutPerms :: + (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2) => + LLVMFieldPerm w sz2 -> NatRepr sz1 -> EndianForm -> + Mb (RNil :> BVType sz1) (ValuePerms (RNil :> LLVMPointerType w :> BVType sz1)) +impl1TruncateLLVMWordFieldOutPerms fp sz1 endianness = + nu $ \y -> + let p_y = + case llvmFieldContents fp of + ValPerm_Eq (PExpr_LLVMWord (bvMatchConst -> Just bv)) + | Just (bv1,_) <- bvSplit endianness sz1 bv -> + ValPerm_Eq (bvBV bv1) + ValPerm_Eq (PExpr_LLVMWord _) -> ValPerm_True + _ -> + error ("applyImpl1: Impl1_TruncateLLVMWordField: " + ++ "malformed input permission") in + MNil :>: ValPerm_LLVMField (llvmFieldSetEqWordVar fp y) :>: p_y + + +-- | Helper function to compute the output permissions of the +-- 'Impl1_ConcatLLVMWordFields' rule +impl1ConcatLLVMWordFieldsOutPerms :: + (1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2, + 1 <= (sz1 + sz2), KnownNat (sz1 + sz2)) => + LLVMFieldPerm w sz1 -> PermExpr (BVType sz2) -> EndianForm -> + Mb (RNil :> BVType (sz1 + sz2)) (ValuePerms (RNil :> LLVMPointerType w :> + BVType (sz1 + sz2))) +impl1ConcatLLVMWordFieldsOutPerms fp1 e2 endianness = + nu $ \y -> + let p_y = + case (llvmFieldContents fp1, bvMatchConst e2) of + (ValPerm_Eq (PExpr_LLVMWord + (bvMatchConst -> Just bv1)), Just bv2) -> + ValPerm_Eq $ bvBV (bvConcat endianness bv1 bv2) + (ValPerm_Eq (PExpr_LLVMWord _), _) -> ValPerm_True + _ -> + error ("applyImpl1: Impl1_ConcatLLVMWordField: " + ++ "malformed input permission") in + MNil :>: ValPerm_LLVMField (llvmFieldSetEqWordVar fp1 y) :>: p_y + + instance SubstVar PermVarSubst m => Substable PermVarSubst (EqPerm a) m where genSubst s (mbMatch -> [nuMP| EqPerm x e b |]) = EqPerm <$> genSubst s x <*> genSubst s e <*> return (mbLift b) @@ -2552,15 +2634,6 @@ instance SubstVar PermVarSubst m => genSubst s fld [nuMP| SImpl_DemoteLLVMFieldRW x fld |] -> SImpl_DemoteLLVMFieldRW <$> genSubst s x <*> genSubst s fld - [nuMP| SImpl_SplitLLVMWordField x fp bv1 bv2 endianness |] -> - SImpl_SplitLLVMWordField <$> genSubst s x <*> genSubst s fp <*> - return (mbLift bv1) <*> return (mbLift bv2) <*> return (mbLift endianness) - [nuMP| SImpl_TruncateLLVMWordField x fp bv1 endianness |] -> - SImpl_TruncateLLVMWordField <$> genSubst s x <*> genSubst s fp <*> - return (mbLift bv1) <*> return (mbLift endianness) - [nuMP| SImpl_ConcatLLVMWordFields x fp1 bv2 endianness |] -> - SImpl_ConcatLLVMWordFields <$> genSubst s x <*> genSubst s fp1 <*> - return (mbLift bv2) <*> return (mbLift endianness) [nuMP| SImpl_SplitLLVMTrueField x fp sz1 sz2m1 |] -> SImpl_SplitLLVMTrueField <$> genSubst s x <*> genSubst s fp <*> return (mbLift sz1) <*> return (mbLift sz2m1) @@ -2750,6 +2823,15 @@ instance SubstVar PermVarSubst m => Impl1_ElimLLVMFieldContents <$> genSubst s x <*> genSubst s fp [nuMP| Impl1_ElimLLVMBlockToEq x bp |] -> Impl1_ElimLLVMBlockToEq <$> genSubst s x <*> genSubst s bp + [nuMP| Impl1_SplitLLVMWordField x fp2 sz1 endianness |] -> + Impl1_SplitLLVMWordField <$> genSubst s x <*> genSubst s fp2 <*> + return (mbLift sz1) <*> return (mbLift endianness) + [nuMP| Impl1_TruncateLLVMWordField x fp2 sz1 endianness |] -> + Impl1_TruncateLLVMWordField <$> genSubst s x <*> genSubst s fp2 <*> + return (mbLift sz1) <*> return (mbLift endianness) + [nuMP| Impl1_ConcatLLVMWordFields x fp1 e2 endianness |] -> + Impl1_ConcatLLVMWordFields <$> genSubst s x <*> genSubst s fp1 <*> + genSubst s e2 <*> return (mbLift endianness) [nuMP| Impl1_BeginLifetime |] -> return Impl1_BeginLifetime [nuMP| Impl1_TryProveBVProp x prop prop_str |] -> Impl1_TryProveBVProp <$> genSubst s x <*> genSubst s prop <*> @@ -4129,25 +4211,25 @@ implLLVMFieldSetTrue x fp = introLLVMFieldContentsM x y fp_true -- | Start with a pointer permission on top of the stack and try to coerce it to --- a pointer permission whose contents are of the form @(eq(llvmword(bv)))@ --- where @bv@ is a constant bitvector. If successful, return @bv@, otherwise --- coerce to a field with @true@ contents and return 'Nothing'. +-- a pointer permission whose contents are of the form @(eq(llvmword(e)))@. If +-- successful, return @e@, otherwise coerce to a field with @true@ contents and +-- return 'Nothing'. implLLVMFieldTryProveWordEq :: (NuMatchingAny1 r, 1 <= w, KnownNat w, 1 <= sz, KnownNat sz) => ExprVar (LLVMPointerType w) -> LLVMFieldPerm w sz -> ImplM vars s r (ps :> LLVMPointerType w) (ps :> LLVMPointerType w) - (Maybe (BV.BV sz)) + (Maybe (PermExpr (BVType sz))) implLLVMFieldTryProveWordEq x fp = implElimLLVMFieldContentsM x fp >>>= \y -> getPerm y >>>= \p -> implPushM y p >>> elimOrsExistsNamesM y >>>= \case ValPerm_Eq e -> substEqsWithProof e >>>= \eqp -> case someEqProofRHS eqp of - PExpr_LLVMWord (bvMatchConst -> Just bv) -> + PExpr_LLVMWord e' -> implCastPermM y (fmap ValPerm_Eq eqp) >>> - let fp' = llvmFieldSetEqWord fp bv in + let fp' = llvmFieldSetEqWord fp e' in introLLVMFieldContentsM x y fp' >>> - return (Just bv) + return (Just e') _ -> implDropM y p >>> implLLVMFieldSetTrue x (llvmFieldSetEqVar fp y) >>> return Nothing @@ -4163,7 +4245,7 @@ implLLVMFieldTryProveWordEq2 :: ExprVar (LLVMPointerType w) -> LLVMFieldPerm w sz1 -> LLVMFieldPerm w sz2 -> ImplM vars s r (ps :> LLVMPointerType w :> LLVMPointerType w) (ps :> LLVMPointerType w :> LLVMPointerType w) - (Maybe (BV.BV sz1, BV.BV sz2)) + (Maybe (PermExpr (BVType sz1), PermExpr (BVType sz2))) implLLVMFieldTryProveWordEq2 x fp1 fp2 = implLLVMFieldTryProveWordEq x fp2 >>>= \case Nothing -> @@ -4173,8 +4255,8 @@ implLLVMFieldTryProveWordEq2 x fp1 fp2 = let fp1_true = llvmFieldSetTrue fp1 fp1 in implSwapM x (ValPerm_LLVMField fp2_true) x (ValPerm_LLVMField fp1_true) >>> return Nothing - Just bv2 -> - let fp2' = llvmFieldSetEqWord fp2 bv2 in + Just e2 -> + let fp2' = llvmFieldSetEqWord fp2 e2 in implSwapM x (ValPerm_LLVMField fp1) x (ValPerm_LLVMField fp2') >>> implLLVMFieldTryProveWordEq x fp1 >>>= \case Nothing -> @@ -4182,10 +4264,10 @@ implLLVMFieldTryProveWordEq2 x fp1 fp2 = implSwapM x (ValPerm_LLVMField fp2') x (ValPerm_LLVMField fp1_true) >>> implLLVMFieldSetTrue x fp2' >>> return Nothing - Just bv1 -> - let fp1' = llvmFieldSetEqWord fp2 bv1 in + Just e1 -> + let fp1' = llvmFieldSetEqWord fp2 e1 in implSwapM x (ValPerm_LLVMField fp2') x (ValPerm_LLVMField fp1') >>> - return (Just (bv1, bv2)) + return (Just (e1, e2)) -- | Attempt to split a pointer permission @ptr((rw,off,sz) |-> p)@ on top of -- the stack into two permissions of the form @ptr((rw,off,8*len) |-> p1)@ and @@ -4209,19 +4291,15 @@ implLLVMFieldSplit x fp sz_bytes withKnownNat sz $ withKnownNat fp_m_sz $ use implStateEndianness >>>= \endianness -> implLLVMFieldTryProveWordEq x fp >>>= \case - Just bv - | Just (bv1, bv2) <- bvSplit endianness sz bv -> - implSimplM Proxy (SImpl_SplitLLVMWordField - x (llvmFieldSetEqWord fp bv) bv1 bv2 endianness) >>> - return (Perm_LLVMField (llvmFieldSetEqWord fp bv1), - Perm_LLVMField (llvmFieldAddOffsetInt - (llvmFieldSetEqWord fp bv2) - sz_bytes)) - Just _ -> - -- NOTE: this is unreachable because we already know that sz <= - -- llvmFieldSize (because the subNat' above succeeded), so the bvSplit - -- above should always succeed - error "implLLVMFieldSplit: unreachable case" + Just e -> + implApplyImpl1 + (Impl1_SplitLLVMWordField x (llvmFieldSetEqWord fp e) sz endianness) + (MNil :>: Impl1Cont (const $ return ())) >>> + getDistPerms >>>= + \(_ :>: VarAndPerm _ (ValPerm_Conj1 p1) :>: + VarAndPerm _ (ValPerm_Conj1 p2) :>: + VarAndPerm y p_y :>: VarAndPerm z p_z) -> + implPopM z p_z >>> implPopM y p_y >>> return (p1,p2) Nothing -> implSimplM Proxy (SImpl_SplitLLVMTrueField x (llvmFieldSetTrue fp fp) sz fp_m_sz) >>> @@ -4230,14 +4308,15 @@ implLLVMFieldSplit x fp sz_bytes (llvmFieldSetTrue fp fp_m_sz) sz_bytes)) implLLVMFieldSplit _ _ _ = - implFailMsgM "implLLVMFieldSplit: malformed input permissions" - + error "implLLVMFieldSplit: malformed input permissions" -- | Attempt to truncate a pointer permission @ptr((rw,off,sz) |-> p)@ on top of -- the stack into a permission of the form @ptr((rw,off,sz') |-> p')@ for @sz'@ -- smaller than @sz@. If @p@ can be coerced to an equality permission -- @eq(llvmword(bv))@ for a known constant bitvector @bv@, then @p'@ is an --- equality to the truncation of @bv@; otherwise it is just @true@. +-- equality to the truncation of @bv@. If @p@ can be coerced to an equality +-- permission @eq(llvmword(e))@ to some non-constant @e@, @p'@ is an equality to +-- a fresh bitvector variable. Otherwise @p'@ is just @true@. implLLVMFieldTruncate :: (NuMatchingAny1 r, 1 <= w, KnownNat w, 1 <= sz, KnownNat sz) => ExprVar (LLVMPointerType w) -> LLVMFieldPerm w sz -> NatRepr sz' -> @@ -4250,29 +4329,28 @@ implLLVMFieldTruncate x fp sz' withKnownNat sz' $ use implStateEndianness >>>= \endianness -> implLLVMFieldTryProveWordEq x fp >>>= \case - Just bv - | Just (bv', _) <- bvSplit endianness sz' bv -> - implSimplM Proxy (SImpl_TruncateLLVMWordField - x (llvmFieldSetEqWord fp bv) bv' endianness) >>> - return (Perm_LLVMField (llvmFieldSetEqWord fp bv')) - Just _ -> - -- NOTE: this is unreachable because we already know that sz <= - -- llvmFieldSize (because the subNat' above succeeded), so the bvSplit - -- above should always succeed - error "implLLVMFieldTruncate: unreachable case" + Just e -> + implApplyImpl1 + (Impl1_TruncateLLVMWordField x (llvmFieldSetEqWord fp e) sz' endianness) + (MNil :>: Impl1Cont (const $ return ())) >>> + getDistPerms >>>= + \(_ :>: VarAndPerm _ (ValPerm_Conj1 p) :>: VarAndPerm y p_y) -> + implPopM y p_y >>> return p Nothing -> implSimplM Proxy (SImpl_TruncateLLVMTrueField x (llvmFieldSetTrue fp fp) sz') >>> return (Perm_LLVMField (llvmFieldSetTrue fp sz')) implLLVMFieldTruncate _ _ _ = - implFailMsgM "implLLVMFieldTruncate: malformed input permissions" + error "implLLVMFieldTruncate: malformed input permissions" -- | Concatentate two pointer permissions @ptr((rw,off,sz1) |-> p1)@ and -- @ptr((rw,off+sz1/8,sz2) |-> p2)@ into a single pointer permission of the form -- @ptr((rw,off,sz1+sz2) |-> p)@. If @p1@ and @p2@ are both equality permissions -- @eq(llvmword(bv))@ for known constant bitvectors, then the output contents --- permission @p@ is an equality to the concatenated of these bitvectors. --- Otherwise @p@ is just @true@. +-- permission @p@ is an equality to the concatenated of these bitvectors. If +-- @p1@ and @p2@ are both equality permissions to bitvector expressions (at +-- least one of which is non-constant), then @p@ is an equality to a fresh +-- variable. Otherwise @p@ is just @true@. implLLVMFieldConcat :: (NuMatchingAny1 r, 1 <= w, KnownNat w, 1 <= sz1, KnownNat sz1, 1 <= sz2, KnownNat sz2) => @@ -4281,18 +4359,23 @@ implLLVMFieldConcat :: (ps :> LLVMPointerType w :> LLVMPointerType w) () implLLVMFieldConcat x fp1 fp2 - | LeqProof <- leqAddPos fp1 fp2 = + | LeqProof <- leqAddPos fp1 fp2 + , fp1 == llvmFieldSetContents fp2 (llvmFieldContents fp1) = withKnownNat (addNat (natRepr fp1) (natRepr fp2)) $ use implStateEndianness >>>= \endianness -> implLLVMFieldTryProveWordEq2 x fp1 fp2 >>>= \case + Just (e1, e2) -> + implApplyImpl1 + (Impl1_ConcatLLVMWordFields x (llvmFieldSetEqWord fp1 e1) e2 endianness) + (MNil :>: Impl1Cont (const $ return ())) >>> + getDistPerms >>>= \(_ :>: VarAndPerm y p_y) -> + implPopM y p_y Nothing -> implSimplM Proxy (SImpl_ConcatLLVMTrueFields x (llvmFieldSetTrue fp1 fp1) (llvmFieldSize fp2)) - Just (bv1, bv2) -> - implSimplM Proxy (SImpl_ConcatLLVMWordFields x - (llvmFieldSetEqWord fp1 bv1) - bv2 endianness) +implLLVMFieldConcat _ _ _ = + error "implLLVMFieldConcat: malformed input permissions" -- | Borrow a cell from an LLVM array permission on the top of the stack, after -- proving (with 'implTryProveBVProps') that the index is in the array exclusive diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs index 20b9a62bc9..7237ba2642 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Permissions.hs @@ -693,6 +693,14 @@ mbExprLLVMTypeWidth :: KnownNat w => Mb ctx (f (LLVMPointerType w)) -> NatRepr w mbExprLLVMTypeWidth _ = knownNat +-- | Convenience function to get the bit width of a bitvector type +exprBVTypeWidth :: KnownNat w => f (BVType w) -> NatRepr w +exprBVTypeWidth _ = knownNat + +-- | Convenience function to get the bit width of an LLVM pointer type +mbExprBVTypeWidth :: KnownNat w => Mb ctx (f (BVType w)) -> NatRepr w +mbExprBVTypeWidth _ = knownNat + -- | Convenience function to get the bit width of an LLVM pointer type shapeLLVMTypeWidth :: KnownNat w => f (LLVMShapeType w) -> NatRepr w shapeLLVMTypeWidth _ = knownNat @@ -3486,11 +3494,11 @@ llvmFieldSetContents :: LLVMFieldPerm w sz1 -> llvmFieldSetContents (LLVMFieldPerm {..}) p = LLVMFieldPerm { llvmFieldContents = p, .. } --- | Set the contents of a field permission to an @eq(llvmword(bv))@ permission +-- | Set the contents of a field permission to an @eq(llvmword(e))@ permission llvmFieldSetEqWord :: (1 <= sz2, KnownNat sz2) => LLVMFieldPerm w sz1 -> - BV.BV sz2 -> LLVMFieldPerm w sz2 -llvmFieldSetEqWord fp bv = - llvmFieldSetContents fp (ValPerm_Eq $ PExpr_LLVMWord $ bvBV bv) + PermExpr (BVType sz2) -> LLVMFieldPerm w sz2 +llvmFieldSetEqWord fp e = + llvmFieldSetContents fp (ValPerm_Eq $ PExpr_LLVMWord e) -- | Set the contents of a field permission to an @eq(y)@ permission llvmFieldSetEqVar :: (1 <= sz2, KnownNat sz2) => LLVMFieldPerm w sz1 -> @@ -3498,6 +3506,12 @@ llvmFieldSetEqVar :: (1 <= sz2, KnownNat sz2) => LLVMFieldPerm w sz1 -> llvmFieldSetEqVar fp y = llvmFieldSetContents fp (ValPerm_Eq $ PExpr_Var y) +-- | Set the contents of a field permission to an @eq(llvmword(y))@ permission +llvmFieldSetEqWordVar :: (1 <= sz2, KnownNat sz2) => LLVMFieldPerm w sz1 -> + ExprVar (BVType sz2) -> LLVMFieldPerm w sz2 +llvmFieldSetEqWordVar fp y = + llvmFieldSetContents fp (ValPerm_Eq $ PExpr_LLVMWord $ PExpr_Var y) + -- | Set the contents of a field permission to an @true@ permission of a -- specific size llvmFieldSetTrue :: (1 <= sz2, KnownNat sz2) => LLVMFieldPerm w sz1 -> diff --git a/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs b/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs index 404bb85562..955d0ebb63 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs @@ -786,6 +786,28 @@ bvMulOpenTerm n x y = applyOpenTermMulti (globalOpenTerm "Prelude.bvMul") [natOpenTerm n, x, y] +bvSplitOpenTerm :: EndianForm -> OpenTerm -> OpenTerm -> OpenTerm -> + (OpenTerm, OpenTerm) +bvSplitOpenTerm BigEndian sz1 sz2 e = + (applyOpenTermMulti (globalOpenTerm "Prelude.take") [boolTypeOpenTerm, + sz1, sz2, e], + applyOpenTermMulti (globalOpenTerm "Prelude.drop") [boolTypeOpenTerm, + sz1, sz2, e]) +bvSplitOpenTerm LittleEndian sz1 sz2 e = + (applyOpenTermMulti (globalOpenTerm "Prelude.drop") [boolTypeOpenTerm, + sz2, sz1, e], + applyOpenTermMulti (globalOpenTerm "Prelude.take") [boolTypeOpenTerm, + sz2, sz1, e]) + +bvConcatOpenTerm :: EndianForm -> OpenTerm -> OpenTerm -> + OpenTerm -> OpenTerm -> OpenTerm +bvConcatOpenTerm BigEndian sz1 sz2 e1 e2 = + applyOpenTermMulti (globalOpenTerm "Prelude.append") + [boolTypeOpenTerm, sz1, sz2, boolTypeOpenTerm, e1, e2] +bvConcatOpenTerm LittleEndian sz1 sz2 e1 e2 = + applyOpenTermMulti (globalOpenTerm "Prelude.append") + [boolTypeOpenTerm, sz2, sz1, boolTypeOpenTerm, e2, e1] + -- | Translate a variable to a 'Member' proof, raising an error if the variable -- is unbound translateVar :: Mb ctx (ExprVar a) -> Member ctx a @@ -2418,24 +2440,6 @@ translateSimplImpl (ps0 :: Proxy ps0) mb_simpl m = case mbMatch mb_simpl of ptrans']) m - [nuMP| SImpl_SplitLLVMWordField x _ _ _ _ |] -> - do ttrans <- translateSimplImplOut mb_simpl - withPermStackM (:>: translateVar x) - (\(pctx :>: _) -> RL.append pctx $ typeTransF ttrans []) - m - - [nuMP| SImpl_TruncateLLVMWordField _ _ _ _ |] -> - do ttrans <- translateSimplImplOut mb_simpl - withPermStackM id - (\(pctx :>: _) -> RL.append pctx $ typeTransF ttrans []) - m - - [nuMP| SImpl_ConcatLLVMWordFields _ _ _ _ |] -> - do ttrans <- translateSimplImplOut mb_simpl - withPermStackM RL.tail - (\(pctx :>: _ :>: _) -> RL.append pctx $ typeTransF ttrans []) - m - [nuMP| SImpl_SplitLLVMTrueField x _ _ _ |] -> do ttrans <- translateSimplImplOut mb_simpl withPermStackM (:>: translateVar x) @@ -3457,6 +3461,88 @@ translatePermImpl1 prx mb_impl mb_impls = case (mbMatch mb_impl, mbMatch mb_impl typeTransF tp_trans2 [transTerm1 ptrans]) m + ([nuMP| Impl1_SplitLLVMWordField _ mb_fp mb_sz1 mb_endianness |], _) -> + translatePermImplUnary mb_impls $ \m -> + do let mb_e = case mbLLVMFieldContents mb_fp of + [nuP| ValPerm_Eq (PExpr_LLVMWord e) |] -> e + _ -> error "translatePermImpl1: Impl1_SplitLLVMWordField" + e_tm <- translate1 mb_e + sz1_tm <- translate mb_sz1 + sz2_tm <- translateClosed $ mbLLVMFieldSize mb_fp + let sz2m1_tm = + applyOpenTermMulti (globalOpenTerm "Prelude.subNat") [sz2_tm, + sz1_tm] + let (e1_tm,e2_tm) = + bvSplitOpenTerm (mbLift mb_endianness) sz1_tm sz2m1_tm e_tm + inExtTransM (ETrans_Term e1_tm) $ inExtTransM (ETrans_Term e2_tm) $ + translate + (mbCombine RL.typeCtxProxies $ flip mbMapCl mb_fp + ($(mkClosed + [| \sz1 endianness fp -> + impl1SplitLLVMWordFieldOutPerms fp sz1 endianness |]) + `clApply` toClosed (mbLift mb_sz1) + `clApply` toClosed (mbLift mb_endianness))) >>= \pctx_out -> + withPermStackM + (\(vars :>: x) -> vars :>: x :>: x :>: + Member_Step Member_Base :>: Member_Base) + (\(pctx :>: _) -> + -- NOTE: all output perms are eq or ptr to eq perms, so contain no + -- SAW core terms + pctx `RL.append` typeTransF pctx_out []) + m + + ([nuMP| Impl1_TruncateLLVMWordField _ mb_fp mb_sz1 mb_endianness |], _) -> + translatePermImplUnary mb_impls $ \m -> + do let mb_e = case mbLLVMFieldContents mb_fp of + [nuP| ValPerm_Eq (PExpr_LLVMWord e) |] -> e + _ -> error "translatePermImpl1: Impl1_TruncateLLVMWordField" + e_tm <- translate1 mb_e + sz1_tm <- translate mb_sz1 + sz2_tm <- translateClosed $ mbLLVMFieldSize mb_fp + let sz2m1_tm = + applyOpenTermMulti (globalOpenTerm "Prelude.subNat") [sz2_tm, + sz1_tm] + let (e1_tm,_) = + bvSplitOpenTerm (mbLift mb_endianness) sz1_tm sz2m1_tm e_tm + inExtTransM (ETrans_Term e1_tm) $ + translate + (mbCombine RL.typeCtxProxies $ flip mbMapCl mb_fp + ($(mkClosed + [| \sz1 endianness fp -> + impl1TruncateLLVMWordFieldOutPerms fp sz1 endianness |]) + `clApply` toClosed (mbLift mb_sz1) + `clApply` toClosed (mbLift mb_endianness))) >>= \pctx_out -> + withPermStackM (:>: Member_Base) + (\(pctx :>: _) -> + -- NOTE: all output perms are eq or ptr to eq perms, so contain no + -- SAW core terms + pctx `RL.append` typeTransF pctx_out []) + m + + ([nuMP| Impl1_ConcatLLVMWordFields _ mb_fp1 mb_e2 mb_endianness |], _) -> + translatePermImplUnary mb_impls $ \m -> + do let mb_e1 = case mbLLVMFieldContents mb_fp1 of + [nuP| ValPerm_Eq (PExpr_LLVMWord e1) |] -> e1 + _ -> error "translatePermImpl1: Impl1_ConcatLLVMWordFields" + e1_tm <- translate1 mb_e1 + e2_tm <- translate1 mb_e2 + sz1_tm <- translateClosed $ mbLLVMFieldSize mb_fp1 + sz2_tm <- translateClosed $ mbExprBVTypeWidth mb_e2 + let endianness = mbLift mb_endianness + let e_tm = bvConcatOpenTerm endianness sz1_tm sz2_tm e1_tm e2_tm + inExtTransM (ETrans_Term e_tm) $ + translate (mbCombine RL.typeCtxProxies $ + mbMap2 (\fp1 e2 -> + impl1ConcatLLVMWordFieldsOutPerms fp1 e2 endianness) + mb_fp1 mb_e2) >>= \pctx_out -> + withPermStackM + (\(vars :>: x :>: _) -> (vars :>: x :>: Member_Base)) + (\(pctx :>: _ :>: _) -> + -- NOTE: all output perms are eq or ptr to eq perms, so contain no + -- SAW core terms + pctx `RL.append` typeTransF pctx_out []) + m + ([nuMP| Impl1_BeginLifetime |], _) -> translatePermImplUnary mb_impls $ \m -> inExtTransM ETrans_Lifetime $ From 304aa527ca020ecd83c6b4de50a336b4d63487f9 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Sun, 21 Nov 2021 14:49:09 -0800 Subject: [PATCH 12/18] added the structs example to test out C structs with padding --- heapster-saw/examples/_CoqProject | 2 ++ heapster-saw/examples/structs.bc | Bin 0 -> 4592 bytes heapster-saw/examples/structs.c | 25 +++++++++++++++ heapster-saw/examples/structs.saw | 49 ++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 heapster-saw/examples/structs.bc create mode 100644 heapster-saw/examples/structs.c create mode 100644 heapster-saw/examples/structs.saw diff --git a/heapster-saw/examples/_CoqProject b/heapster-saw/examples/_CoqProject index 878793f383..122a716fa3 100644 --- a/heapster-saw/examples/_CoqProject +++ b/heapster-saw/examples/_CoqProject @@ -8,6 +8,8 @@ xor_swap_gen.v xor_swap_proofs.v xor_swap_rust_gen.v xor_swap_rust_proofs.v +structs_gen.v +structs_proofs.v string_set_gen.v string_set_proofs.v loops_gen.v diff --git a/heapster-saw/examples/structs.bc b/heapster-saw/examples/structs.bc new file mode 100644 index 0000000000000000000000000000000000000000..ca4fd4b3bddd012450d5a7444fe217086ef87a0c GIT binary patch literal 4592 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 HA3y#AHH7 +#include + +typedef struct padded_struct { + uint64_t padded1; + uint8_t padded2; + uint64_t padded3; + uint8_t padded4; +} padded_struct; + +padded_struct *alloc_padded_struct (void) { + padded_struct *ret = malloc (sizeof(padded_struct)); + ret->padded1 = 0; + ret->padded2 = 0; + ret->padded3 = 0; + ret->padded4 = 0; + return ret; +} + +void padded_struct_incr_all (padded_struct *p) { + p->padded1++; + p->padded2++; + p->padded3++; + p->padded4++; +} diff --git a/heapster-saw/examples/structs.saw b/heapster-saw/examples/structs.saw new file mode 100644 index 0000000000..443f38d77e --- /dev/null +++ b/heapster-saw/examples/structs.saw @@ -0,0 +1,49 @@ + +enable_experimental; +env <- heapster_init_env "structs" "structs.bc"; + +/*** + *** Type Definitions + ***/ + +// Integer types +heapster_define_perm env "int8" " " "llvmptr 8" "exists x:bv 8.eq(llvmword(x))"; +heapster_define_perm env "int64" " " "llvmptr 64" "exists x:bv 64.eq(llvmword(x))"; + +// padded_struct type +heapster_define_llvmshape env "u64" 64 "" "fieldsh(int64<>)"; + +heapster_define_llvmshape env "padded_struct" 64 "" + "fieldsh(int64<>);fieldsh(8,int8<>);fieldsh(56,true); \ + \ fieldsh(int64<>);fieldsh(8,int8<>);fieldsh(56,true)"; + + +/*** + *** Assumed Functions + ***/ + +heapster_assume_fun env "malloc" + "(sz:bv 64). arg0:eq(llvmword(8*sz)) -o \ + \ arg0:true, ret:array(W,0, \ + \ returnM (BVVec 64 sz #()) \ + \ (genBVVec 64 sz #() (\\ (i:Vec 64 Bool) (_:is_bvult 64 i sz) -> ()))"; + + +/*** + *** Type-Checked Functions + ***/ + +// alloc_padded_struct +heapster_typecheck_fun env "alloc_padded_struct" + "(). empty -o ret:memblock(W,0,32,padded_struct<>)"; + +// padded_struct_incr_all +heapster_typecheck_fun env "padded_struct_incr_all" + "(). arg0:memblock(W,0,32,padded_struct<>) -o arg0:memblock(W,0,32,padded_struct<>)"; + +/*** + *** Export to Coq + ***/ + +heapster_export_coq env "structs_gen.v"; From 45853fdecd38e3293a6d3804360c85615490a50f Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Sun, 21 Nov 2021 14:53:43 -0800 Subject: [PATCH 13/18] renamed structs example to c_data --- heapster-saw/examples/_CoqProject | 4 +-- .../examples/{structs.bc => c_data.bc} | Bin heapster-saw/examples/{structs.c => c_data.c} | 0 .../examples/{structs.saw => c_data.saw} | 4 +-- heapster-saw/examples/c_data_proofs.v | 26 ++++++++++++++++++ 5 files changed, 30 insertions(+), 4 deletions(-) rename heapster-saw/examples/{structs.bc => c_data.bc} (100%) rename heapster-saw/examples/{structs.c => c_data.c} (100%) rename heapster-saw/examples/{structs.saw => c_data.saw} (92%) create mode 100644 heapster-saw/examples/c_data_proofs.v diff --git a/heapster-saw/examples/_CoqProject b/heapster-saw/examples/_CoqProject index 122a716fa3..6ace3f2f96 100644 --- a/heapster-saw/examples/_CoqProject +++ b/heapster-saw/examples/_CoqProject @@ -8,8 +8,8 @@ xor_swap_gen.v xor_swap_proofs.v xor_swap_rust_gen.v xor_swap_rust_proofs.v -structs_gen.v -structs_proofs.v +c_data_gen.v +c_data_proofs.v string_set_gen.v string_set_proofs.v loops_gen.v diff --git a/heapster-saw/examples/structs.bc b/heapster-saw/examples/c_data.bc similarity index 100% rename from heapster-saw/examples/structs.bc rename to heapster-saw/examples/c_data.bc diff --git a/heapster-saw/examples/structs.c b/heapster-saw/examples/c_data.c similarity index 100% rename from heapster-saw/examples/structs.c rename to heapster-saw/examples/c_data.c diff --git a/heapster-saw/examples/structs.saw b/heapster-saw/examples/c_data.saw similarity index 92% rename from heapster-saw/examples/structs.saw rename to heapster-saw/examples/c_data.saw index 443f38d77e..7f53838aa6 100644 --- a/heapster-saw/examples/structs.saw +++ b/heapster-saw/examples/c_data.saw @@ -1,6 +1,6 @@ enable_experimental; -env <- heapster_init_env "structs" "structs.bc"; +env <- heapster_init_env "c_data" "c_data.bc"; /*** *** Type Definitions @@ -46,4 +46,4 @@ heapster_typecheck_fun env "padded_struct_incr_all" *** Export to Coq ***/ -heapster_export_coq env "structs_gen.v"; +heapster_export_coq env "c_data_gen.v"; diff --git a/heapster-saw/examples/c_data_proofs.v b/heapster-saw/examples/c_data_proofs.v new file mode 100644 index 0000000000..429a0c657c --- /dev/null +++ b/heapster-saw/examples/c_data_proofs.v @@ -0,0 +1,26 @@ +From Coq Require Import Lists.List. +From Coq Require Import String. +From Coq Require Import Vectors.Vector. +From CryptolToCoq Require Import SAWCoreScaffolding. +From CryptolToCoq Require Import SAWCoreVectorsAsCoqVectors. +From CryptolToCoq Require Import SAWCoreBitvectors. + +From CryptolToCoq Require Import SAWCorePrelude. +From CryptolToCoq Require Import CompMExtra. + +Require Import Examples.c_data_gen. +Import c_data. + +Import SAWCorePrelude. + +Lemma no_errors_alloc_padded_struct : + refinesFun alloc_padded_struct noErrorsSpec. + unfold alloc_padded_struct, alloc_padded_struct__tuple_fun, noErrorsSpec, malloc. + time "no_errors_alloc_padded_struct" prove_refinement. +Qed. + +Lemma no_errors_padded_struct_incr_all : + refinesFun padded_struct_incr_all (fun _ => noErrorsSpec). + unfold padded_struct_incr_all, padded_struct_incr_all__tuple_fun, noErrorsSpec. + time "no_errors_padded_struct_incr_all" prove_refinement. +Qed. From 38d6d853565015543b4d90b9f63f3da95138ac93 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Sun, 21 Nov 2021 15:15:07 -0800 Subject: [PATCH 14/18] added the incr_u64_ptr_byte example to c_data to test out the new field split and truncate rules --- heapster-saw/examples/c_data.bc | Bin 4592 -> 5024 bytes heapster-saw/examples/c_data.c | 8 ++++++++ heapster-saw/examples/c_data.saw | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/heapster-saw/examples/c_data.bc b/heapster-saw/examples/c_data.bc index ca4fd4b3bddd012450d5a7444fe217086ef87a0c..08e4e2186088fd1584d6aa5a6dd8f6fdb87eb4f2 100644 GIT binary patch delta 2102 zcmZ`)eQZ-z6hE);@%q-jcI{T+jc(>`?VxxxZEwR&SKxX=0+kt})$~qL$ z*td2rWf^2!NgNPO(Wyf)PDo7kuMTj`1aw4@{b2-*20|1e(LW4{^W1je2YS*=K z&Uxqj?(My|Y?%ed?{ahXfX#xUmJS%*RB*t%l@bO%6um7rh%p)IFegn4oP%+^+ke0& z?HhbARWg5?>gf_*YwL9=b@dX}w5aRth7oRQ|6+}wxyI#0t-WZ=Fu)0x;@S8fZW?^* zgZvTo1ptwJEZEcG{Kj!Tvu3bCiXHR5%nhFC)7mxEH|dXys!K-bCu|Fgs&v~; z`bL{Rb+PK+V(Df2Ruo^F)-Q?&Vx1j3L!D729BvJ}3WK{acnU*J z&ufJwoe6QajZ3={Vug(>aVNxT8#m~_U~wuc0+?Ww#R2QKHJtjjyGsM226E-Z)x$UY z&pG{x<)zvswxglNF;_^x>X7x_sqB|`Iuv5B zEL{~VJ8G?-D1IuhKW1ATd&gQ`OQ(P5H^^hLbx*rF)ja3ses~RWzn~aRM zNd{=!7U~G>Tu~Eny5l3R^qGJ0!Vhml@NELI% zsP5P!Yj>L1v=z79JNf{6V(qQ zNeKOs5PDXyv{?u4BtvtX(xK={%A0>ZuvI~9`a*_H_h;2OzX=I#*GD(y; z%Cji*P@*U#oEe3*JfJYmJCNxvl#&@6!IAR|c7R@-6P<`@#?CDPID~WZa#o_0&e#e5 zBb<{hjn`mDvLt~xT=)d%b5T~J%$czh2hyJZtI7{aMfY7>k;sef%fi9U%fhXph|6s1OJW~_>Yk3FU$+b%P`aa1gAzcIRF3v delta 1574 zcmZWpZ)_7~7=NyJyWYCnas89+x)ye8HyCCv*KJ*pkZ@<6Ln+!hFjHsfzsVBn=-42N zCcUj4sk)Jk8wsE$m0$wNFpGRazgYfN#5ODhl}|*J2r;O9AbycBpSKlE^vyl*`#!(- z_dM@&zxUqDt-0qFZshB00Gk8tx^+(Ipv-gHPO5J1l>Ah1jL9jzy1QkIwI%qF=4s!9 ztB$^N8BSE?zDX_4gyo$0%SVCi(^f}r8`F8BWYslcLj!YKpm;WQWK;h$)yA!wz!PB) z07rHyx7FcIevK2s^a#i^{K-OoKnLj&0?0YUzD$Gq7*Qyji+R6W1c*-%kmT!Fby##- zgR|4st9uJfU|)}#T+rU;_DzM@meA>w=0+2B)%d2m#deb3>|Ohu$Fgh5{kcBS+vYAE zv}oqB@!fl3<6}mzWz3O`#N$Kp?G>ov_>9Jyy;KMGL4&PvrYdM>W$41~3wuZ*0LtV& z(aVK=llLSqcLfL8%e~lf&7nkeTxFt!1C%`NsO`704Cq*Tl0Zczr1{UQ)!*~9f3TC6 zA|)kWVOmsawFMFA8-)sn5{h1HD#rCOk||&_~}>&0``PbmCiRSvL`((|SHV zdoFvH(q%~&eqUZ*UhxHX8y`HxUWtVi+e*l1H4T^Bw)oJOR)WgL*6@0Sj&fYMkPA1j z2WViA_*#!n2$1`rDYfo^Gkr|E1F*j~`=hL*Gl9+tDPG7W+4Z2e@0TNQ@D}qYG)UdD zDFehgMnf((>rDUTMDCZ;WY1Aw>#B?Z;p;kAXF7~dRsj8=hTP#C4R@m4(cwZ)7cpOi z_Jm0#x{Q^)%4%4OX9^?J&a|fivzf{t7CGeCn6N%S_RLhOeeb%G8LdX~<*Gt@Sgf}rRzV_ZeWMat@6lxPxUj?Je3LIDG1 zLBAq`7A414*AfLG<y7|v%XC86>uiV8}wmG6j! z258j0J}|3vvQtAOg@`F4eoRr{tVDo!i2)VtEVQIC-4Ci6mM)52*QT{mCsG`M83t?d z6srVGc%Ho=inxVZ!X2x+G26J0>GQ`zVRj`z)dXf32$&(M^)q%hzexEQ2Di8~t<6 #include +/* Increment the first byte pointed to by a 64-bit word pointer */ +void incr_u64_ptr_byte (uint64_t *x) { + uint8_t *x_byte = (uint8_t*)x; + (*x_byte)++; +} + typedef struct padded_struct { uint64_t padded1; uint8_t padded2; @@ -8,6 +14,7 @@ typedef struct padded_struct { uint8_t padded4; } padded_struct; +/* Allocated a padded_struct */ padded_struct *alloc_padded_struct (void) { padded_struct *ret = malloc (sizeof(padded_struct)); ret->padded1 = 0; @@ -17,6 +24,7 @@ padded_struct *alloc_padded_struct (void) { return ret; } +/* Increment all fields of a padded_struct */ void padded_struct_incr_all (padded_struct *p) { p->padded1++; p->padded2++; diff --git a/heapster-saw/examples/c_data.saw b/heapster-saw/examples/c_data.saw index 7f53838aa6..fd21846443 100644 --- a/heapster-saw/examples/c_data.saw +++ b/heapster-saw/examples/c_data.saw @@ -34,6 +34,10 @@ heapster_assume_fun env "malloc" *** Type-Checked Functions ***/ +// incr_u64_ptr_byte +heapster_typecheck_fun env "incr_u64_ptr_byte" + "(). arg0:ptr((W,0) |-> int64<>) -o arg0:ptr((W,0) |-> int64<>)"; + // alloc_padded_struct heapster_typecheck_fun env "alloc_padded_struct" "(). empty -o ret:memblock(W,0,32,padded_struct<>)"; From 0762a7f8a9d405929773be228d3ea784b4608bca Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Sun, 21 Nov 2021 15:15:49 -0800 Subject: [PATCH 15/18] whoops, fixed a bug in implLLVMFieldTryProveWordEq2 and removed the incorrect check in implLLVMFieldConcat --- heapster-saw/src/Verifier/SAW/Heapster/Implication.hs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs index 96b75d8ae3..efcb718cf8 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs @@ -4265,7 +4265,7 @@ implLLVMFieldTryProveWordEq2 x fp1 fp2 = implLLVMFieldSetTrue x fp2' >>> return Nothing Just e1 -> - let fp1' = llvmFieldSetEqWord fp2 e1 in + let fp1' = llvmFieldSetEqWord fp1 e1 in implSwapM x (ValPerm_LLVMField fp2') x (ValPerm_LLVMField fp1') >>> return (Just (e1, e2)) @@ -4359,8 +4359,7 @@ implLLVMFieldConcat :: (ps :> LLVMPointerType w :> LLVMPointerType w) () implLLVMFieldConcat x fp1 fp2 - | LeqProof <- leqAddPos fp1 fp2 - , fp1 == llvmFieldSetContents fp2 (llvmFieldContents fp1) = + | LeqProof <- leqAddPos fp1 fp2 = withKnownNat (addNat (natRepr fp1) (natRepr fp2)) $ use implStateEndianness >>>= \endianness -> implLLVMFieldTryProveWordEq2 x fp1 fp2 >>>= \case @@ -4374,8 +4373,6 @@ implLLVMFieldConcat x fp1 fp2 implSimplM Proxy (SImpl_ConcatLLVMTrueFields x (llvmFieldSetTrue fp1 fp1) (llvmFieldSize fp2)) -implLLVMFieldConcat _ _ _ = - error "implLLVMFieldConcat: malformed input permissions" -- | Borrow a cell from an LLVM array permission on the top of the stack, after -- proving (with 'implTryProveBVProps') that the index is in the array exclusive From 6a2bafc4a4ba4182c4587e04ce9c680d36f54f82 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Sun, 21 Nov 2021 15:23:54 -0800 Subject: [PATCH 16/18] whoops, forgot to coerce the output pointer permission to have the proper output contents after concatenating --- heapster-saw/src/Verifier/SAW/Heapster/Implication.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs index efcb718cf8..824e900bd5 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/Implication.hs @@ -5731,8 +5731,11 @@ proveVarLLVMFieldH2 x (Perm_LLVMField fp) off mb_fp withExtVarsM (proveVarImplInt x $ mbValPerm_LLVMField mb_fp2) >>> getTopDistPerm x >>>= \(ValPerm_LLVMField fp2) -> - -- Finally, combine these two pieces of mb_fp into a single permission - implLLVMFieldConcat x fp1 fp2 + -- Finally, combine these two pieces of mb_fp into a single permission, and + -- use this permission to prove the one we needed to begin with + implLLVMFieldConcat x fp1 fp2 >>> + getTopDistPerm x >>>= \(ValPerm_LLVMField fp_concat) -> + proveVarLLVMFieldH x (Perm_LLVMField fp_concat) off mb_fp -- If we have a field permission that contains the correct offset but doesn't -- start at it, then split it and recurse From f01247c0af10009c8b7d89c6172a750dba26d505 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Sun, 21 Nov 2021 15:29:53 -0800 Subject: [PATCH 17/18] whoops, incorrect arguments to Prelude.append in the translation --- heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs b/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs index 955d0ebb63..b1006d1b7b 100644 --- a/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs +++ b/heapster-saw/src/Verifier/SAW/Heapster/SAWTranslation.hs @@ -803,10 +803,10 @@ bvConcatOpenTerm :: EndianForm -> OpenTerm -> OpenTerm -> OpenTerm -> OpenTerm -> OpenTerm bvConcatOpenTerm BigEndian sz1 sz2 e1 e2 = applyOpenTermMulti (globalOpenTerm "Prelude.append") - [boolTypeOpenTerm, sz1, sz2, boolTypeOpenTerm, e1, e2] + [sz1, sz2, boolTypeOpenTerm, e1, e2] bvConcatOpenTerm LittleEndian sz1 sz2 e1 e2 = applyOpenTermMulti (globalOpenTerm "Prelude.append") - [boolTypeOpenTerm, sz2, sz1, boolTypeOpenTerm, e2, e1] + [sz2, sz1, boolTypeOpenTerm, e2, e1] -- | Translate a variable to a 'Member' proof, raising an error if the variable -- is unbound From e2b68b6c9b2cd3fc44e5f3f3ad17cc72b43496e3 Mon Sep 17 00:00:00 2001 From: Eddy Westbrook Date: Sun, 21 Nov 2021 15:30:17 -0800 Subject: [PATCH 18/18] added no-errors proof for the new incr_u64_ptr_byte example --- heapster-saw/examples/c_data_proofs.v | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/heapster-saw/examples/c_data_proofs.v b/heapster-saw/examples/c_data_proofs.v index 429a0c657c..505387e908 100644 --- a/heapster-saw/examples/c_data_proofs.v +++ b/heapster-saw/examples/c_data_proofs.v @@ -13,6 +13,12 @@ Import c_data. Import SAWCorePrelude. +Lemma no_errors_incr_u64_ptr_byte : + refinesFun incr_u64_ptr_byte (fun _ => noErrorsSpec). + unfold incr_u64_ptr_byte, incr_u64_ptr_byte__tuple_fun, noErrorsSpec. + time "no_errors_incr_u64_ptr_byte" prove_refinement. +Qed. + Lemma no_errors_alloc_padded_struct : refinesFun alloc_padded_struct noErrorsSpec. unfold alloc_padded_struct, alloc_padded_struct__tuple_fun, noErrorsSpec, malloc.