Skip to content

Commit

Permalink
Merge pull request #1920 from GaloisInc/import-foreign
Browse files Browse the repository at this point in the history
Allow importing Cryptol `foreign` functions
  • Loading branch information
mergify[bot] authored Sep 1, 2023
2 parents 79ddd80 + 7980fb8 commit d565fe6
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 106 deletions.
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

0 comments on commit d565fe6

Please sign in to comment.