Skip to content

Commit 4c0985c

Browse files
authored
Merge pull request #1925 from GaloisInc/T1859-mir_array_value
`mir_array_value`
2 parents aecabca + 76e523d commit 4c0985c

File tree

23 files changed

+324
-47
lines changed

23 files changed

+324
-47
lines changed

crucible-mir-comp/src/Mir/Compositional/Builder.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -685,14 +685,14 @@ regToSetup bak p eval shp rv = go shp rv
685685
msbPrePost p . seVars %= Set.insert (Some var)
686686
liftIO $ MS.SetupTerm <$> eval btpr expr
687687
go (TupleShape _ _ flds) rvs = MS.SetupStruct () <$> goFields flds rvs
688-
go (ArrayShape _ _ shp) vec = do
688+
go (ArrayShape _ elemTy shp) vec = do
689689
svs <- case vec of
690690
MirVector_Vector v -> mapM (go shp) (toList v)
691691
MirVector_PartialVector v -> forM (toList v) $ \p -> do
692692
rv <- liftIO $ readMaybeType sym "vector element" (shapeType shp) p
693693
go shp rv
694694
MirVector_Array _ -> error $ "regToSetup: MirVector_Array NYI"
695-
return $ MS.SetupArray () svs
695+
return $ MS.SetupArray elemTy svs
696696
go (StructShape _ _ flds) (AnyValue tpr rvs)
697697
| Just Refl <- testEquality tpr shpTpr =
698698
MS.SetupStruct () <$> goFields flds rvs

crucible-mir-comp/src/Mir/Compositional/Override.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ matchArg sym sc eval allocSpecs md shp rv sv = go shp rv sv
391391
show (W4.printSymExpr val))
392392
""
393393
go (TupleShape _ _ flds) rvs (MS.SetupStruct () svs) = goFields flds rvs svs
394-
go (ArrayShape _ _ shp) vec (MS.SetupArray () svs) = case vec of
394+
go (ArrayShape _ _ shp) vec (MS.SetupArray _ svs) = case vec of
395395
MirVector_Vector v -> zipWithM_ (\x y -> go shp x y) (toList v) svs
396396
MirVector_PartialVector pv -> forM_ (zip (toList pv) svs) $ \(p, sv) -> do
397397
rv <- liftIO $ readMaybeType sym "vector element" (shapeType shp) p

doc/manual/manual.md

+7
Original file line numberDiff line numberDiff line change
@@ -2621,6 +2621,13 @@ the value of an array element.
26212621
* `jvm_field_is : JVMValue -> String -> JVMValue -> JVMSetup ()`
26222622
specifies the name of an object field.
26232623

2624+
In the experimental MIR verification implementation, the following functions
2625+
construct compound values:
2626+
2627+
* `mir_array_value : MIRType -> [SetupValue] -> SetupValue` constructs an array
2628+
of the given type whose elements consist of the given values. Supplying the
2629+
element type is necessary to support length-0 arrays.
2630+
26242631
### Bitfields
26252632

26262633
SAW has experimental support for specifying `struct`s with bitfields, such as
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
all: test.linked-mir.json
2+
3+
test.linked-mir.json: test.rs
4+
saw-rustc $<
5+
$(MAKE) remove-unused-build-artifacts
6+
7+
.PHONY: remove-unused-build-artifacts
8+
remove-unused-build-artifacts:
9+
rm -f test libtest.mir libtest.rlib
10+
11+
.PHONY: clean
12+
clean: remove-unused-build-artifacts
13+
rm -f test.linked-mir.json

intTests/test_mir_verify_arrays/test.linked-mir.json

+1
Large diffs are not rendered by default.
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
pub fn f(x: [&u32; 2]) -> u32 {
2+
x[0].wrapping_add(*x[1])
3+
}
4+
5+
pub fn g(x: [&mut u32; 2]) {
6+
*x[0] = 42;
7+
*x[1] = x[1].wrapping_add(1);
8+
}
9+
10+
pub fn h() -> [&'static u32; 2] {
11+
[&27, &42]
12+
}
13+
14+
pub fn i(_x: [u32; 0]) -> [u64; 0] {
15+
[]
16+
}
+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
enable_experimental;
2+
3+
let f_spec = do {
4+
x0_ref <- mir_alloc mir_u32;
5+
x0 <- mir_fresh_var "x0" mir_u32;
6+
mir_points_to x0_ref (mir_term x0);
7+
8+
x1_ref <- mir_alloc mir_u32;
9+
x1 <- mir_fresh_var "x1" mir_u32;
10+
mir_points_to x1_ref (mir_term x1);
11+
12+
let x = mir_array_value (mir_ref mir_u32) [x0_ref, x1_ref];
13+
14+
mir_execute_func [x];
15+
16+
mir_return (mir_term {{ x0 + x1 }});
17+
};
18+
19+
let g_spec = do {
20+
x0_ref <- mir_alloc_mut mir_u32;
21+
22+
x1_ref <- mir_alloc_mut mir_u32;
23+
x1 <- mir_fresh_var "x1" mir_u32;
24+
mir_points_to x1_ref (mir_term x1);
25+
26+
let x = mir_array_value (mir_ref_mut mir_u32) [x0_ref, x1_ref];
27+
28+
mir_execute_func [x];
29+
30+
mir_points_to x0_ref (mir_term {{ 42 : [32] }});
31+
mir_points_to x1_ref (mir_term {{ x1 + 1 }});
32+
};
33+
34+
let h_spec = do {
35+
mir_execute_func [];
36+
37+
x0_ref <- mir_alloc mir_u32;
38+
mir_points_to x0_ref (mir_term {{ 27 : [32] }});
39+
40+
x1_ref <- mir_alloc mir_u32;
41+
mir_points_to x1_ref (mir_term {{ 42 : [32] }});
42+
43+
let x = mir_array_value (mir_ref mir_u32) [x0_ref, x1_ref];
44+
mir_return x;
45+
};
46+
47+
let i_spec = do {
48+
let x = mir_term {{ [] : [0][32] }};
49+
mir_execute_func [x];
50+
51+
mir_return (mir_array_value mir_u64 []);
52+
};
53+
54+
let i_spec_bad1 = do {
55+
let x = mir_term {{ [42] : [1][32] }};
56+
mir_execute_func [x];
57+
58+
mir_return (mir_array_value mir_u64 []);
59+
};
60+
61+
let i_spec_bad2 = do {
62+
let x = mir_term {{ [] : [0][32] }};
63+
mir_execute_func [x];
64+
65+
mir_return (mir_array_value mir_u64 [mir_term {{ 42 : [64] }}]);
66+
};
67+
68+
m <- mir_load_module "test.linked-mir.json";
69+
70+
mir_verify m "test::f" [] false f_spec z3;
71+
mir_verify m "test::g" [] false g_spec z3;
72+
mir_verify m "test::h" [] false h_spec z3;
73+
mir_verify m "test::i" [] false i_spec z3;
74+
75+
fails (
76+
mir_verify m "test::i" [] false i_spec_bad1 z3
77+
);
78+
fails (
79+
mir_verify m "test::i" [] false i_spec_bad2 z3
80+
);
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
set -e
2+
3+
$SAW test.saw

saw-remote-api/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
how SAW's MIR verification support works in general, see the `mir_*` commands
1414
documented in the [SAW
1515
manual](https://github.com/GaloisInc/saw-script/blob/master/doc/manual/manual.md).
16+
* The API for `"array"` `setup value`s now has an `"element type"` field. For
17+
LLVM verification, this field is optional. For MIR verification, this field
18+
is required if the `"elements"` value is empty and optional if the
19+
`"elements"` value is non-empty.
1620

1721
## 1.0.0 -- 2023-06-26
1822

saw-remote-api/python/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
For more information about how SAW's MIR verification support works in
1313
general, see the `mir_*` commands documented in the [SAW
1414
manual](https://github.com/GaloisInc/saw-script/blob/master/doc/manual/manual.md).
15+
* The `array()` function now takes an additional `element_type` argument, which
16+
defaults to `None`. If constructing a MIR array with no elements, then the
17+
`element_type` must be specified. Otherwise, this argument is optional.
1518

1619
## 1.0.1 -- YYYY-MM-DD
1720

saw-remote-api/python/saw_client/crucible.py

+18-6
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,23 @@ def to_json(self) -> JSON:
197197
return {'setup value': 'null'}
198198

199199
class ArrayVal(SetupVal):
200+
element_type : Union['LLVMType', 'JVMType', 'MIRType', None]
200201
elements : List[SetupVal]
201202

202-
def __init__(self, elements : List[SetupVal]) -> None:
203+
def __init__(self,
204+
element_type : Union['LLVMType', 'JVMType', 'MIRType', None],
205+
elements : List[SetupVal]) -> None:
206+
self.element_type = element_type
203207
self.elements = elements
204208

205209
def to_json(self) -> JSON:
210+
element_type_json: Optional[Any]
211+
if self.element_type is None:
212+
element_type_json = None
213+
else:
214+
element_type_json = self.element_type.to_json()
206215
return {'setup value': 'array',
216+
'element type' : element_type_json,
207217
'elements': [element.to_json() for element in self.elements]}
208218

209219
name_regexp = re.compile('^(?P<prefix>.*[^0-9])?(?P<number>[0-9]+)?$')
@@ -668,16 +678,18 @@ def cry_f(s : str) -> CryptolTerm:
668678
"""
669679
return CryptolTerm(to_cryptol_str_customf(s, frames=1))
670680

671-
def array(*elements: SetupVal) -> SetupVal:
681+
def array(*elements: SetupVal, element_type: Union['LLVMType', 'JVMType', 'MIRType', None] = None) -> SetupVal:
672682
"""Returns an array with the provided ``elements`` (i.e., an ``ArrayVal``).
683+
If returning an empty MIR array, you are required to explicitly supply
684+
an ``element_type``; otherwise, the ``element_type`` argument is optional.
673685
674-
N.B., one or more ``elements`` must be provided.""" # FIXME why? document this here when we figure it out.
675-
if len(elements) == 0:
676-
raise ValueError('An array must be constructed with one or more elements')
686+
If returning an LLVM array, then one or more ``elements`` must be provided.""" # FIXME why? document this here when we figure it out.
687+
if isinstance(element_type, LLVMType) and len(elements) == 0:
688+
raise ValueError('An LLVM array must be constructed with one or more elements')
677689
for e in elements:
678690
if not isinstance(e, SetupVal):
679691
raise ValueError('array expected a SetupVal, but got {e!r}')
680-
return ArrayVal(list(elements))
692+
return ArrayVal(element_type, list(elements))
681693

682694
def elem(base: SetupVal, index: int) -> SetupVal:
683695
"""Returns the value of the array element at position ``index`` in ``base`` (i.e., an ``ElemVal``).

saw-remote-api/python/tests/saw/test-files/mir_arrays.linked-mir.json

+1
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
pub fn f(x: [&u32; 2]) -> u32 {
2+
x[0].wrapping_add(*x[1])
3+
}
4+
5+
pub fn g(x: [&mut u32; 2]) {
6+
*x[0] = 42;
7+
*x[1] = x[1].wrapping_add(1);
8+
}
9+
10+
pub fn h() -> [&'static u32; 2] {
11+
[&27, &42]
12+
}
13+
14+
pub fn i(_x: [u32; 0]) -> [u64; 0] {
15+
[]
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import unittest
2+
from pathlib import Path
3+
4+
from saw_client import *
5+
from saw_client.crucible import array, cry, cry_f
6+
from saw_client.mir import Contract, FreshVar, MIRType, SetupVal, u32, u64, void
7+
8+
9+
def ref_to_fresh(c : Contract, ty : MIRType, name : Optional[str] = None,
10+
read_only : bool = False) -> Tuple[FreshVar, SetupVal]:
11+
"""Add to ``Contract`` ``c`` an allocation of a reference of type ``ty`` initialized to an unknown fresh value.
12+
If ``read_only == True`` then the allocated memory is immutable.
13+
14+
:returns A fresh variable bound to the reference's initial value and the newly allocated reference. (The fresh
15+
variable will be assigned ``name`` if provided/available.)"""
16+
var = c.fresh_var(ty, name)
17+
ptr = c.alloc(ty, points_to = var, read_only = read_only)
18+
return (var, ptr)
19+
20+
21+
class FContract(Contract):
22+
def specification(self) -> None:
23+
(x0, x0_p) = ref_to_fresh(self, u32, "x0", read_only = True)
24+
(x1, x1_p) = ref_to_fresh(self, u32, "x1", read_only = True)
25+
x = array(x0_p, x1_p)
26+
27+
self.execute_func(x)
28+
29+
self.returns_f('{x0} + {x1}')
30+
31+
32+
class GContract(Contract):
33+
def specification(self) -> None:
34+
x0_p = self.alloc(u32)
35+
(x1, x1_p) = ref_to_fresh(self, u32, "x1")
36+
x = array(x0_p, x1_p)
37+
38+
self.execute_func(x)
39+
40+
self.points_to(x0_p, cry_f('42 : [32]'))
41+
self.points_to(x1_p, cry_f('{x1} + 1'))
42+
self.returns(void)
43+
44+
45+
class HContract(Contract):
46+
def specification(self) -> None:
47+
self.execute_func()
48+
49+
x0_ptr = self.alloc(u32, points_to = cry_f('27 : [32]'), read_only = True)
50+
x1_ptr = self.alloc(u32, points_to = cry_f('42 : [32]'), read_only = True)
51+
self.returns(array(x0_ptr, x1_ptr))
52+
53+
54+
class IContract(Contract):
55+
def specification(self) -> None:
56+
self.execute_func(cry_f('[] : [0][32]'))
57+
58+
self.returns(array(element_type = u64))
59+
60+
61+
class MIRArraysTest(unittest.TestCase):
62+
def test_mir_points_to(self):
63+
connect(reset_server=True)
64+
if __name__ == "__main__": view(LogResults())
65+
json_name = str(Path('tests', 'saw', 'test-files', 'mir_arrays.linked-mir.json'))
66+
mod = mir_load_module(json_name)
67+
68+
f_result = mir_verify(mod, 'mir_arrays::f', FContract())
69+
self.assertIs(f_result.is_success(), True)
70+
71+
g_result = mir_verify(mod, 'mir_arrays::g', GContract())
72+
self.assertIs(g_result.is_success(), True)
73+
74+
h_result = mir_verify(mod, 'mir_arrays::h', HContract())
75+
self.assertIs(h_result.is_success(), True)
76+
77+
i_result = mir_verify(mod, 'mir_arrays::i', IContract())
78+
self.assertIs(i_result.is_success(), True)
79+
80+
81+
if __name__ == "__main__":
82+
unittest.main()

saw-remote-api/src/SAWServer.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ instance Show SAWTask where
106106

107107
data CrucibleSetupVal ty e
108108
= NullValue
109-
| ArrayValue [CrucibleSetupVal ty e]
109+
| ArrayValue (Maybe ty) [CrucibleSetupVal ty e]
110110
| TupleValue [CrucibleSetupVal ty e]
111111
-- | RecordValue [(String, CrucibleSetupVal e)]
112112
| FieldLValue (CrucibleSetupVal ty e) String

saw-remote-api/src/SAWServer/Data/SetupValue.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
module SAWServer.Data.SetupValue (CrucibleSetupVal) where
55

66
import Control.Applicative
7-
import Data.Aeson (FromJSON(..), withObject, withText, (.:))
7+
import Data.Aeson (FromJSON(..), withObject, withText, (.:), (.:?))
88

99
import SAWServer
1010

@@ -47,7 +47,7 @@ instance (FromJSON ty, FromJSON cryptolExpr) => FromJSON (CrucibleSetupVal ty cr
4747
TagNamedValue -> NamedValue <$> o .: "name"
4848
TagNullValue -> pure NullValue
4949
TagCryptol -> CryptolExpr <$> o .: "expression"
50-
TagArrayValue -> ArrayValue <$> o .: "elements"
50+
TagArrayValue -> ArrayValue <$> o .:? "element type" <*> o .: "elements"
5151
TagTupleValue -> TupleValue <$> o .: "elements"
5252
TagFieldLValue -> FieldLValue <$> o .: "base" <*> o .: "field"
5353
TagCastLValue -> CastLValue <$> o .: "base" <*> o .: "type"

saw-remote-api/src/SAWServer/JVMCrucibleSetup.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ compileJVMContract fileReader bic cenv0 c =
180180
resolve env n >>= \case Val x -> return x
181181
getSetupVal (_, cenv) (CryptolExpr expr) =
182182
MS.SetupTerm <$> getTypedTerm cenv expr
183-
getSetupVal _ (ArrayValue _) =
183+
getSetupVal _ (ArrayValue _ _) =
184184
JVMSetupM $ fail "Array setup values unsupported in JVM API."
185185
getSetupVal _ (TupleValue _) =
186186
JVMSetupM $ fail "Tuple setup values unsupported in JVM API."

saw-remote-api/src/SAWServer/LLVMCrucibleSetup.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ compileLLVMContract fileReader bic ghostEnv cenv0 c =
176176
CrucibleSetupVal JSONLLVMType (P.Expr P.PName) ->
177177
LLVMCrucibleSetupM (CMS.AllLLVM MS.SetupValue)
178178
getSetupVal _ NullValue = LLVMCrucibleSetupM $ return CMS.anySetupNull
179-
getSetupVal env (ArrayValue elts) =
179+
getSetupVal env (ArrayValue _ elts) =
180180
do elts' <- mapM (getSetupVal env) elts
181181
LLVMCrucibleSetupM $ return $ CMS.anySetupArray elts'
182182
getSetupVal env (TupleValue elts) =

0 commit comments

Comments
 (0)