Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow importing Cryptol foreign functions #1920

Merged
merged 11 commits into from
Sep 1, 2023
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

## New Features
* SAW now supports loading and reasoning about Cryptol declarations that make use of numeric constraint guards. For more information on numeric constraint guards, see the [relavent section of the Cryptol reference manual](https://galoisinc.github.io/cryptol/master/BasicSyntax.html#numeric-constraint-guards).

* Add an experimental `mir_verify` command, along with related utilities for
constructing specifications for MIR/Rust programs. For more information, see
the `mir_*` commands documented in the [SAW
manual](https://github.com/GaloisInc/saw-script/blob/master/doc/manual/manual.md).

* SAW now supports importing Cryptol modules containing [`foreign`
declarations](https://galoisinc.github.io/cryptol/master/FFI.html). For more
information, see the
[manual](https://github.com/GaloisInc/saw-script/blob/master/doc/manual/manual.md#cryptol-and-its-role-in-saw).

# Version 1.0 -- 2023-06-26

## New Features
Expand Down
238 changes: 137 additions & 101 deletions cryptol-saw-core/src/Verifier/SAW/Cryptol.hs
Original file line number Diff line number Diff line change
Expand Up @@ -744,15 +744,25 @@ importPrimitive sc primOpts env n sch
-- Optionally, create an opaque constant representing the primitive
-- if it doesn't match one of the ones we know about.
| Just _ <- C.asPrim n, allowUnknownPrimitives primOpts =
do t <- importSchema sc env sch
nmi <- importName n
scOpaqueConstant sc nmi t
importOpaque sc env n sch

-- Panic if we don't know the given primitive (TODO? probably shouldn't be a panic)
| Just nm <- C.asPrim n = panic "Unknown Cryptol primitive name" [show nm]

| otherwise = panic "Improper Cryptol primitive name" [show n]

-- | Create an opaque constant with the given name and schema
importOpaque :: SharedContext -> Env -> C.Name -> C.Schema -> IO Term
importOpaque sc env n sch = do
t <- importSchema sc env sch
nmi <- importName n
scOpaqueConstant sc nmi t

importConstant :: SharedContext -> Env -> C.Name -> C.Schema -> Term -> IO Term
importConstant sc env n sch rhs = do
nmi <- importName n
t <- importSchema sc env sch
scConstant' sc nmi rhs t

allPrims :: Map C.PrimIdent (SharedContext -> IO Term)
allPrims = prelPrims <> arrayPrims <> floatPrims <> suiteBPrims <> primeECPrims
Expand Down Expand Up @@ -1393,124 +1403,150 @@ importName cnm =
-- definitions. (With subterm sharing, this is not as bad as it might
-- seem.) We might want to think about generating let or where
-- expressions instead.
--
-- For Cryptol @foreign@ declarations, we import them as regular
-- Cryptol expressions if a Cryptol implementation exists, and as an
-- opaque constant otherwise.
importDeclGroup :: DeclGroupOptions -> SharedContext -> Env -> C.DeclGroup -> IO Env

importDeclGroup declOpts sc env (C.Recursive [decl]) =
case C.dDefinition decl of
C.DPrim ->
panic "importDeclGroup" ["Primitive declarations cannot be recursive:", show (C.dName decl)]

C.DForeign {} ->
error "`foreign` imports may not be used in SAW specifications"

C.DExpr expr ->
do env1 <- bindName sc (C.dName decl) (C.dSignature decl) env
t' <- importSchema sc env (C.dSignature decl)
e' <- importExpr' sc env1 (C.dSignature decl) expr
let x = nameToLocalName (C.dName decl)
f' <- scLambda sc x t' e'
rhs <- scGlobalApply sc "Prelude.fix" [t', f']
rhs' <- case declOpts of
TopLevelDeclGroup _ ->
do nmi <- importName (C.dName decl)
scConstant' sc nmi rhs t'
NestedDeclGroup -> return rhs
let env' = env { envE = Map.insert (C.dName decl) (rhs', 0) (envE env)
, envC = Map.insert (C.dName decl) (C.dSignature decl) (envC env) }
return env'


-- - A group of mutually-recursive declarations -
-- We handle this by "tupling up" all the declarations using a record and
-- taking the fixpoint at this record type. The desired declarations are then
-- achieved by projecting the field names from this record.
importDeclGroup declOpts sc env (C.Recursive decls) =
do -- build the environment for the declaration bodies
let dm = Map.fromList [ (C.dName d, d) | d <- decls ]

-- grab a reference to the outermost variable; this will be the record in the body
-- of the lambda we build later
v0 <- scLocalVar sc 0

-- build a list of projections from a record variable
vm <- traverse (scRecordSelect sc v0 . nameToFieldName . C.dName) dm

-- the types of the declarations
tm <- traverse (importSchema sc env . C.dSignature) dm
-- the type of the recursive record
rect <- scRecordType sc (Map.assocs $ Map.mapKeys nameToFieldName tm)

let env1 = liftEnv env
let env2 = env1 { envE = Map.union (fmap (\v -> (v, 0)) vm) (envE env1)
, envC = Map.union (fmap C.dSignature dm) (envC env1)
, envS = rect : envS env1 }

let extractDeclExpr decl =
case C.dDefinition decl of
C.DExpr expr -> importExpr' sc env2 (C.dSignature decl) expr
C.DForeign {} ->
error "`foreign` imports may not be used in SAW specifications"
C.DPrim ->
case decls of

[decl] ->
case C.dDefinition decl of

C.DPrim ->
panic "importDeclGroup"
["Primitive declarations cannot be recursive:", show (C.dName decl)]

C.DForeign _ mexpr ->
case mexpr of
Nothing -> panicForeignNoExpr decl
Just expr -> addExpr expr

C.DExpr expr -> addExpr expr

where
addExpr expr = do
env1 <- bindName sc (C.dName decl) (C.dSignature decl) env
t' <- importSchema sc env (C.dSignature decl)
e' <- importExpr' sc env1 (C.dSignature decl) expr
let x = nameToLocalName (C.dName decl)
f' <- scLambda sc x t' e'
rhs <- scGlobalApply sc "Prelude.fix" [t', f']
rhs' <- case declOpts of
TopLevelDeclGroup _ ->
do nmi <- importName (C.dName decl)
scConstant' sc nmi rhs t'
NestedDeclGroup -> return rhs
let env' = env { envE = Map.insert (C.dName decl) (rhs', 0) (envE env)
, envC = Map.insert (C.dName decl) (C.dSignature decl) (envC env) }
return env'

-- - A group of mutually-recursive declarations -
-- We handle this by "tupling up" all the declarations using a record and
-- taking the fixpoint at this record type. The desired declarations are
-- then achieved by projecting the field names from this record.
_ -> do
-- build the environment for the declaration bodies
let dm = Map.fromList [ (C.dName d, d) | d <- decls ]

-- grab a reference to the outermost variable; this will be the record in the body
-- of the lambda we build later
v0 <- scLocalVar sc 0

-- build a list of projections from a record variable
vm <- traverse (scRecordSelect sc v0 . nameToFieldName . C.dName) dm

-- the types of the declarations
tm <- traverse (importSchema sc env . C.dSignature) dm
-- the type of the recursive record
rect <- scRecordType sc (Map.assocs $ Map.mapKeys nameToFieldName tm)

let env1 = liftEnv env
let env2 = env1 { envE = Map.union (fmap (\v -> (v, 0)) vm) (envE env1)
, envC = Map.union (fmap C.dSignature dm) (envC env1)
, envS = rect : envS env1 }

let extractDeclExpr decl =
case C.dDefinition decl of
C.DExpr expr -> importExpr' sc env2 (C.dSignature decl) expr
C.DForeign _ mexpr ->
case mexpr of
Nothing -> panicForeignNoExpr decl
Just expr ->
importExpr' sc env2 (C.dSignature decl) expr
C.DPrim ->
panic "importDeclGroup"
[ "Primitive declarations cannot be recursive:"
, show (C.dName decl)
]

-- the raw imported bodies of the declarations
em <- traverse extractDeclExpr dm
-- the raw imported bodies of the declarations
em <- traverse extractDeclExpr dm

-- the body of the recursive record
recv <- scRecord sc (Map.mapKeys nameToFieldName em)
-- the body of the recursive record
recv <- scRecord sc (Map.mapKeys nameToFieldName em)

-- build a lambda from the record body...
f <- scLambda sc "fixRecord" rect recv
-- build a lambda from the record body...
f <- scLambda sc "fixRecord" rect recv

-- and take its fixpoint
rhs <- scGlobalApply sc "Prelude.fix" [rect, f]
-- and take its fixpoint
rhs <- scGlobalApply sc "Prelude.fix" [rect, f]

-- finally, build projections from the fixed record to shove into the environment
-- if toplevel, then wrap each binding with a Constant constructor
let mkRhs d t =
do let s = nameToFieldName (C.dName d)
r <- scRecordSelect sc rhs s
case declOpts of
TopLevelDeclGroup _ ->
do nmi <- importName (C.dName d)
scConstant' sc nmi r t
NestedDeclGroup -> return r
rhss <- sequence (Map.intersectionWith mkRhs dm tm)
-- finally, build projections from the fixed record to shove into the environment
-- if toplevel, then wrap each binding with a Constant constructor
let mkRhs d t =
do let s = nameToFieldName (C.dName d)
r <- scRecordSelect sc rhs s
case declOpts of
TopLevelDeclGroup _ ->
do nmi <- importName (C.dName d)
scConstant' sc nmi r t
NestedDeclGroup -> return r
rhss <- sequence (Map.intersectionWith mkRhs dm tm)

let env' = env { envE = Map.union (fmap (\v -> (v, 0)) rhss) (envE env)
let env' = env { envE = Map.union (fmap (\v -> (v, 0)) rhss) (envE env)
, envC = Map.union (fmap C.dSignature dm) (envC env)
}
return env'
return env'

importDeclGroup declOpts sc env (C.NonRecursive decl) =
case C.dDefinition decl of
C.DForeign {} ->
error "`foreign` imports may not be used in SAW specifications"
where
panicForeignNoExpr decl = panic "importDeclGroup"
[ "Foreign declaration without Cryptol body in recursive group:"
, show (C.dName decl) ]

importDeclGroup declOpts sc env (C.NonRecursive decl) = do

rhs <- case C.dDefinition decl of
C.DForeign _ mexpr
| TopLevelDeclGroup _ <- declOpts ->
case mexpr of
Nothing -> importOpaque sc env (C.dName decl) (C.dSignature decl)
Just expr -> do
rhs <- importExpr' sc env (C.dSignature decl) expr
importConstant sc env (C.dName decl) (C.dSignature decl) rhs
| otherwise ->
panic "importDeclGroup"
["Foreign declarations only allowed at top-level:", show (C.dName decl)]

C.DPrim
| TopLevelDeclGroup primOpts <- declOpts -> do
rhs <- importPrimitive sc primOpts env (C.dName decl) (C.dSignature decl)
let env' = env { envE = Map.insert (C.dName decl) (rhs, 0) (envE env)
, envC = Map.insert (C.dName decl) (C.dSignature decl) (envC env)
}
return env'
| otherwise -> do
| TopLevelDeclGroup primOpts <- declOpts ->
importPrimitive sc primOpts env (C.dName decl) (C.dSignature decl)
| otherwise -> do
panic "importDeclGroup" ["Primitive declarations only allowed at top-level:", show (C.dName decl)]

C.DExpr expr -> do
rhs <- importExpr' sc env (C.dSignature decl) expr
rhs' <- case declOpts of
TopLevelDeclGroup _ ->
do nmi <- importName (C.dName decl)
t <- importSchema sc env (C.dSignature decl)
scConstant' sc nmi rhs t
NestedDeclGroup -> return rhs
let env' = env { envE = Map.insert (C.dName decl) (rhs', 0) (envE env)
, envC = Map.insert (C.dName decl) (C.dSignature decl) (envC env) }
return env'
rhs <- importExpr' sc env (C.dSignature decl) expr
case declOpts of
TopLevelDeclGroup _ ->
importConstant sc env (C.dName decl) (C.dSignature decl) rhs
NestedDeclGroup -> return rhs

pure env { envE = Map.insert (C.dName decl) (rhs, 0) (envE env)
, envC = Map.insert (C.dName decl) (C.dSignature decl) (envC env)
}


data ImportPrimitiveOptions =
ImportPrimitiveOptions
Expand Down Expand Up @@ -1722,7 +1758,7 @@ importMatches sc env (C.Let decl : matches) =
case C.dDefinition decl of

C.DForeign {} ->
error "`foreign` imports may not be used in SAW specifications"
panic "importMatches" ["Foreign declarations not allowed in 'let':", show (C.dName decl)]

C.DPrim -> do
panic "importMatches" ["Primitive declarations not allowed in 'let':", show (C.dName decl)]
Expand Down
2 changes: 1 addition & 1 deletion deps/cryptol
Submodule cryptol updated 45 files
+8 −0 CHANGES.md
+1 −1 cryptol-remote-api/src/CryptolServer/Data/Expression.hs
+16 −0 docs/RefMan/FFI.rst
+14 −13 src/Cryptol/Backend/FFI.hs
+6 −4 src/Cryptol/Backend/FFI/Error.hs
+2 −3 src/Cryptol/Backend/Monad.hs
+35 −16 src/Cryptol/Eval.hs
+12 −11 src/Cryptol/Eval/FFI.hs
+8 −3 src/Cryptol/Eval/Reference.lhs
+1 −1 src/Cryptol/IR/FreeVars.hs
+1 −1 src/Cryptol/IR/TraverseNames.hs
+39 −28 src/Cryptol/ModuleSystem/Base.hs
+42 −18 src/Cryptol/ModuleSystem/Env.hs
+3 −0 src/Cryptol/ModuleSystem/Monad.hs
+5 −2 src/Cryptol/ModuleSystem/Renamer.hs
+2 −2 src/Cryptol/Parser.y
+26 −5 src/Cryptol/Parser/AST.hs
+39 −35 src/Cryptol/Parser/ExpandPropGuards.hs
+14 −8 src/Cryptol/Parser/Names.hs
+69 −27 src/Cryptol/Parser/NoPat.hs
+10 −6 src/Cryptol/Parser/ParserUtils.hs
+41 −0 src/Cryptol/REPL/Monad.hs
+14 −9 src/Cryptol/Transform/MonoValues.hs
+4 −4 src/Cryptol/Transform/Specialize.hs
+1 −1 src/Cryptol/TypeCheck.hs
+23 −13 src/Cryptol/TypeCheck/AST.hs
+82 −75 src/Cryptol/TypeCheck/Infer.hs
+2 −1 src/Cryptol/TypeCheck/Parseable.hs
+12 −7 src/Cryptol/TypeCheck/Sanity.hs
+3 −3 src/Cryptol/TypeCheck/Subst.hs
+5 −0 tests/ffi/ffi-fallback-rec.c
+5 −0 tests/ffi/ffi-fallback-rec.cry
+3 −0 tests/ffi/ffi-fallback-rec.icry
+8 −0 tests/ffi/ffi-fallback-rec.icry.stdout
+8 −0 tests/ffi/ffi-fallback-rec.icry.stdout.darwin
+8 −0 tests/ffi/ffi-fallback-rec.icry.stdout.mingw32
+9 −0 tests/ffi/ffi-fallback.c
+11 −0 tests/ffi/ffi-fallback.cry
+76 −0 tests/ffi/ffi-fallback.icry
+151 −0 tests/ffi/ffi-fallback.icry.stdout
+151 −0 tests/ffi/ffi-fallback.icry.stdout.darwin
+151 −0 tests/ffi/ffi-fallback.icry.stdout.mingw32
+8 −12 tests/ffi/ffi-runtime-errors.icry.stdout
+8 −12 tests/ffi/ffi-runtime-errors.icry.stdout.darwin
+8 −12 tests/ffi/ffi-runtime-errors.icry.stdout.mingw32
8 changes: 7 additions & 1 deletion doc/manual/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,13 @@ In addition to the use of brackets to write Cryptol expressions inline,
several built-in functions can extract `Term` values from Cryptol files
in other ways. The `import` command at the top level imports all
top-level definitions from a Cryptol file and places them in scope
within later bracketed expressions.
within later bracketed expressions. This includes [Cryptol `foreign`
declarations](https://galoisinc.github.io/cryptol/master/FFI.html). If a
[Cryptol implementation of a foreign
function](https://galoisinc.github.io/cryptol/master/FFI.html#cryptol-implementation-of-foreign-functions)
is present, then it will be used as the definition when reasoning about
the function. Otherwise, the function will be imported as an opaque
constant with no definition.

The `cryptol_load` command behaves similarly, but returns a
`CryptolModule` instead. If any `CryptolModule` is in scope, its
Expand Down
Binary file modified doc/manual/manual.pdf
Binary file not shown.
2 changes: 2 additions & 0 deletions intTests/test_import_foreign/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Test that SAW is able to import Cryptol `foreign` functions, and uses the
Cryptol definition when available.
4 changes: 4 additions & 0 deletions intTests/test_import_foreign/test.cry
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
foreign add : [8] -> [8] -> [8]
add x y = x + y

foreign f : [8] -> [8]
7 changes: 7 additions & 0 deletions intTests/test_import_foreign/test.saw
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import "test.cry";

// requires definition of add
prove_print z3 {{ \x y -> add x y == x + y }};

// does not require definition of f
prove_print (unint_z3 ["f"]) {{ \x -> f x + 1 == 1 + f x }};
3 changes: 3 additions & 0 deletions intTests/test_import_foreign/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
set -e

$SAW test.saw
6 changes: 3 additions & 3 deletions src/SAWScript/AutoMatch/Cryptol.hs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ whereBindings _ = Nothing
-- We can't handle primitives currently
declDefExpr :: AST.DeclDef -> Maybe AST.Expr
declDefExpr = \case
AST.DPrim -> Nothing
AST.DForeign {} -> Nothing
AST.DExpr expr -> Just expr
AST.DPrim -> Nothing
AST.DForeign _ mexpr -> mexpr
AST.DExpr expr -> Just expr

-- | If a lambda is of the form @\(a,b,...,z) -> ...)@ then give the list of names bound in the tuple
tupleLambdaBindings :: AST.Expr -> Maybe [AST.Name]
Expand Down
Loading