Merge branch 'master' into conditional-constraints

This commit is contained in:
Henry Blanchette 2022-08-13 14:20:41 -07:00
commit 57cb59a845
87 changed files with 2738 additions and 189 deletions

View File

@ -299,7 +299,9 @@ jobs:
name: test-lib ${{ matrix.target }}
run: |
export PATH=$PWD/bin:$PWD/dist/bin:$PATH
./bin/test-runner --ext=.icry -F -b --exe=dist/bin/cryptol ./tests/${{ matrix.target }}
if ${{ matrix.target != 'ffi' }} || dist/bin/cryptol -v | grep -q 'FFI enabled'; then
./bin/test-runner --ext=.icry -F -b --exe=dist/bin/cryptol ./tests/${{ matrix.target }}
fi
- if: matrix.suite == 'rpc'
shell: bash

14
cry
View File

@ -79,11 +79,21 @@ case $COMMAND in
test)
echo Running tests
setup_external_tools
if [ "$#" == "0" ]; then TESTS=tests; else TESTS=$*; fi
if [ "$#" == "0" ]; then
if cabal v2-exec cryptol -- -v | grep -q 'FFI enabled'; then
TESTS=(tests/*)
else
GLOBIGNORE="tests/ffi"
TESTS=(tests/*)
unset GLOBIGNORE
fi
else
TESTS=("$*")
fi
$BIN/test-runner --ext=.icry \
--exe=cabal \
-F v2-run -F -v0 -F exe:cryptol -F -- -F -b \
$TESTS
"${TESTS[@]}"
;;
rpc-test)

View File

@ -107,6 +107,10 @@ cryptolError modErr warns =
NotAParameterizedModule x ->
(20650, [ ("module", jsonPretty x)
])
FFILoadErrors x errs ->
(20660, [ ("module", jsonPretty x)
, ("errors", jsonList (map jsonPretty errs))
])
OtherFailure x ->
(29999, [ ("error", jsonString x)
])

View File

@ -37,6 +37,10 @@ flag relocatable
default: True
description: Don't use the Cabal-provided data directory for looking up Cryptol libraries. This is useful when the data directory can't be known ahead of time, like for a relocatable distribution.
flag ffi
default: True
description: Enable the foreign function interface
library
Default-language:
Haskell2010
@ -81,6 +85,11 @@ library
else
build-depends: integer-gmp >= 1.0 && < 1.1
if flag(ffi)
build-depends: libffi >= 0.2,
unix
cpp-options: -DFFI_ENABLED
Build-tool-depends: alex:alex, happy:happy
hs-source-dirs: src
@ -148,6 +157,9 @@ library
Cryptol.TypeCheck.TypeMap,
Cryptol.TypeCheck.TypeOf,
Cryptol.TypeCheck.Sanity,
Cryptol.TypeCheck.FFI,
Cryptol.TypeCheck.FFI.Error,
Cryptol.TypeCheck.FFI.FFIType,
Cryptol.TypeCheck.Solver.Types,
Cryptol.TypeCheck.Solver.SMT,
@ -170,6 +182,8 @@ library
Cryptol.Backend,
Cryptol.Backend.Arch,
Cryptol.Backend.Concrete,
Cryptol.Backend.FFI,
Cryptol.Backend.FFI.Error,
Cryptol.Backend.FloatHelpers,
Cryptol.Backend.Monad,
Cryptol.Backend.SeqMap,
@ -180,6 +194,7 @@ library
Cryptol.Eval,
Cryptol.Eval.Concrete,
Cryptol.Eval.Env,
Cryptol.Eval.FFI,
Cryptol.Eval.Generic,
Cryptol.Eval.Prims,
Cryptol.Eval.Reference,

View File

@ -291,7 +291,7 @@ main = do
if Seq.null (rdReplout rd)
then do let cryCmd = (P.shell (exe ++ " --interactive-batch " ++ inFile ++ " -e"))
(cryEC, cryOut, _) <- P.readCreateProcessWithExitCode cryCmd ""
(cryEC, cryOut, cryErr) <- P.readCreateProcessWithExitCode cryCmd ""
Line lnReplinStart _ Seq.:<| _ <- return $ rdReplin rd
@ -301,6 +301,7 @@ main = do
putStrLn $ "REPL error (replin lines " ++
show lnReplinStart ++ "-" ++ show lnReplinEnd ++ ")."
putStr cryOut
putStr cryErr
exitFailure
ExitSuccess -> do
-- remove temporary input file

306
docs/RefMan/FFI.rst Normal file
View File

@ -0,0 +1,306 @@
Foreign Function Interface
==========================
The foreign function interface (FFI) allows Cryptol to call functions written in
C (or other languages that use the C calling convention).
Platform support
----------------
The FFI is currently **not supported on Windows**, and only works on Unix-like
systems (macOS and Linux).
Basic usage
-----------
Suppose we want to call the following C function:
.. code-block:: c
uint32_t add(uint32_t x, uint32_t y) {
return x + y;
}
In our Cryptol file, we write a ``foreign`` declaration with no body:
.. code-block:: cryptol
foreign add : [32] -> [32] -> [32]
The C code must first be compiled into a dynamically loaded shared library. When
Cryptol loads the module containing the ``foreign`` declaration, it will look
for a shared library in the same directory as the Cryptol module, with the same
name as the Cryptol file but with a different file extension. The exact
extension it uses is platform-specific:
* On Linux, it looks for the extension ``.so``.
* On macOS, it looks for the extension ``.dylib``.
For example, if you are on Linux and your ``foreign`` declaration is in
``Foo.cry``, Cryptol will dynamically load ``Foo.so``. Then for each ``foreign``
declaration it will look for a symbol with the same name in the shared library.
So in this case the function we want to call must be bound to the symbol ``add``
in the shared library.
Once the module is loaded, the foreign ``add`` function can be called like any
other Cryptol function. Cryptol automatically converts between Cryptol ``[32]``
values and C ``uint32_t`` values.
The whole process would look something like this:
.. code-block::
$ cc -fPIC -shared Example.c -o Example.so
$ cryptol
Loading module Cryptol
Cryptol> :l Example.cry
Loading module Cryptol
Loading module Main
Main> add 1 2
0x00000003
Note: Since Cryptol currently only accesses the compiled binary and not the C
source, it has no way of checking that the Cryptol function type you declare in
your Cryptol code actually matches the type of the C function. **It is your
responsibility to make sure the types match up**. If they do not then there may
be undefined behavior.
Compiling C code
----------------
Cryptol currently does not handle compilation of C code to shared libraries. For
simple usages, you can do this manually with the following commands:
* Linux: ``cc -fPIC -shared Foo.c -o Foo.so``
* macOS: ``cc -dynamiclib Foo.c -o Foo.dylib``
Converting between Cryptol and C types
--------------------------------------
This section describes how a given Cryptol function signature maps to a C
function prototype. The FFI only supports a limited set of Cryptol types which
have a clear translation into C.
Overall structure
~~~~~~~~~~~~~~~~~
Cryptol ``foreign`` bindings must be functions. These functions may have
multiple (curried) arguments; they may also be polymorphic, with certain
limitations. That is, the general structure of a ``foreign`` declaration would
look something like this:
.. code-block:: cryptol
foreign f : {a1, ..., ak} (c1, ..., cn) => T1 -> ... -> Tm -> Tr
Each type argument to the Cryptol function (``a1, ..., ak`` above) corresponds
to a value argument to the C function. These arguments are passed first, in the
order of the type variables declared in the Cryptol signature, before any
Cryptol value arguments.
Each value argument to the Cryptol function (``T1, ..., Tm`` above) corresponds
to a number of value arguments to the C function. That is, a Cryptol value
argument could correspond to zero, one, or many C arguments. The C arguments for
each Cryptol value argument are passed in the order of the Cryptol value
arguments, after any C arguments corresponding to Cryptol type arguments.
The return value of the Cryptol function (``Tr`` above) is either obtained by
directly returning from the C function or by passing *output arguments* to the C
function, depending on the return type. Output arguments are pointers to memory
which can be modified by the C function to store its output values. If output
arguments are used, they are passed last, after the C arguments corresponding to
Cryptol arguments.
The following tables list the C type(s) that each Cryptol type (or kind)
corresponds to.
Type parameters
~~~~~~~~~~~~~~~
============ ==========
Cryptol kind C type
============ ==========
``#`` ``size_t``
============ ==========
Only numeric type parameters are allowed in polymorphic ``foreign`` functions.
Furthermore, each type parameter ``n`` must satisfy ``fin n``. This has to be
explicitly declared in the Cryptol signature.
Note that if a polymorphic foreign function is called with a type argument that
does not fit in a ``size_t``, there will be a runtime error. (While we could
check this statically by requiring that all type variables in foreign functions
satisfy ``< 2^^64`` instead of just ``fin``, in practice this would be too
cumbersome.)
Bit
~~~
============ ===========
Cryptol type C type
============ ===========
``Bit`` ``uint8_t``
============ ===========
When converting to C, ``True`` is converted to ``1`` and ``False`` to ``0``.
When converting to Cryptol, any nonzero number is converted to ``True`` and
``0`` is converted to ``False``.
Integral types
~~~~~~~~~~~~~~
Let ``K : #`` be a Cryptol type. Note ``K`` must be an actual fixed numeric type
and not a type variable.
================================== ============
Cryptol type C type
================================== ============
``[K]Bit`` where ``0 <= K <= 8`` ``uint8_t``
``[K]Bit`` where ``8 < K <= 16`` ``uint16_t``
``[K]Bit`` where ``16 < K <= 32`` ``uint32_t``
``[K]Bit`` where ``32 < K <= 64`` ``uint64_t``
================================== ============
If the Cryptol type is smaller than the C type, then when converting to C the
value is padded with zero bits, and when converting to Cryptol the extra bits
are ignored. For instance, for the Cryptol type ``[4]``, the Cryptol value ``0xf
: [4]`` is converted to the C value ``uint8_t`` ``0x0f``, and the C ``uint8_t``
``0xaf`` is converted to the Cryptol value ``0xf : [4]``.
Note that words larger than 64 bits are not supported, since there is no
standard C integral type for that. You can split it into a sequence of smaller
words first in Cryptol, then use the FFI conversion for sequences of words to
handle it in C as an array.
Floating point types
~~~~~~~~~~~~~~~~~~~~
============ ==========
Cryptol type C type
============ ==========
``Float32`` ``float``
``Float64`` ``double``
============ ==========
Note: the Cryptol ``Float`` types are defined in the built-in module ``Float``.
Other sizes of floating points are not supported.
Sequences
~~~~~~~~~
Let ``n : #`` be a Cryptol type, possibly containing type variables, that
satisfies ``fin n``, and ``T`` be one of the above Cryptol *integral types* or
*floating point types*. Let ``U`` be the C type that ``T`` corresponds to.
============ ===========
Cryptol type C type
============ ===========
``[n]T`` ``U*``
============ ===========
The C pointer points to an array of ``n`` elements of type ``U``. Note that,
while the length of the array itself is not explicitly passed along with the
pointer, any type arguments contained in the size are passed as C ``size_t``'s
earlier, so the C code can always know the length of the array.
Tuples and records
~~~~~~~~~~~~~~~~~~
Let ``T1, T2, ..., Tn`` be Cryptol types supported by the FFI (which may be any
of the types mentioned above, or tuples and records themselves). Let
``U1, U2, ..., Un`` be the C types that ``T1, T2, ..., Tn`` respectively
correspond to. Let ``f1, f2, ..., fn`` be arbitrary field names.
================================= ===================
Cryptol type C types
================================= ===================
``(T1, T2, ..., Tn)`` ``U1, U2, ..., Un``
``{f1: T1, f2: T2, ..., fn: Tn}`` ``U1, U2, ..., Un``
================================= ===================
In this case, each Cryptol tuple or record is flattened out; passing a tuple as
an argument behaves the same as if you passed its components individually. This
flattening is applied recursively for nested tuples and records. Note that this
means empty tuples don't map to any C values at all.
Type synonyms
~~~~~~~~~~~~~
All type synonyms are expanded before applying the above rules, so you can use
type synonyms in ``foreign`` declarations to improve readability.
Return values
~~~~~~~~~~~~~
If the Cryptol return type is ``Bit`` or one of the above *integral types* or
*floating point types*, the value is returned directly from the C function. In
that case, the return type of the C function will be the C type corresponding to
the Cryptol type, and no extra arguments are added.
If the Cryptol return type is a sequence, tuple, or record, then the value is
returned using output arguments, and the return type of the C function will be
``void``. For tuples and records, each component is recursively returned as
output arguments. When treated as an output argument, each C type ``U`` will be
a pointer ``U*`` instead, except in the case of sequences, where the output and
input versions are the same type, because it is already a pointer.
Quick reference
~~~~~~~~~~~~~~~
================================== =================== ============= =========================
Cryptol type (or kind) C argument type(s) C return type C output argument type(s)
================================== =================== ============= =========================
``#`` ``size_t`` N/A N/A
``Bit`` ``uint8_t`` ``uint8_t`` ``uint8_t*``
``[K]Bit`` where ``0 <= K <= 8`` ``uint8_t`` ``uint8_t`` ``uint8_t*``
``[K]Bit`` where ``8 < K <= 16`` ``uint16_t`` ``uint16_t`` ``uint16_t*``
``[K]Bit`` where ``16 < K <= 32`` ``uint32_t`` ``uint32_t`` ``uint32_t*``
``[K]Bit`` where ``32 < K <= 64`` ``uint64_t`` ``uint64_t`` ``uint64_t*``
``Float32`` ``float`` ``float`` ``float*``
``Float64`` ``double`` ``double`` ``double*``
``[n]T`` ``U*`` N/A ``U*``
``(T1, T2, ..., Tn)`` ``U1, U2, ..., Un`` N/A ``V1, V2, ..., Vn``
``{f1: T1, f2: T2, ..., fn: Tn}`` ``U1, U2, ..., Un`` N/A ``V1, V2, ..., Vn``
================================== =================== ============= =========================
where ``K`` is a constant number, ``n`` is a variable number, ``Ti`` is a type,
``Ui`` is its C argument type, and ``Vi`` is its C output argument type.
Memory
------
When pointers are involved, namely in the cases of sequences and output
arguments, they point to memory. This memory is always allocated and deallocated
by Cryptol; the C code does not need to manage this memory.
In the case of sequences, the pointer will point to an array. In the case of an
output argument for a non-sequence type, the pointer will point to a piece of
memory large enough to hold the given C type, and you should not try to access
any adjacent memory.
For input sequence arguments, the array will already be set to the values
corresponding to the Cryptol values in the sequence. For output arguments, the
memory may be uninitialized when passed to C, and the C code should not read
from it. It must write to the memory the value it is returning.
Evaluation
----------
All Cryptol arguments are fully evaluated when a foreign function is called.
Example
-------
The Cryptol signature
.. code-block:: cryptol
foreign f : {n} (fin n) => [n][10] -> {a : Bit, b : [64]}
-> (Float64, [n + 1][20])
corresponds to the C signature
.. code-block:: c
void f(size_t n, uint16_t *in0, uint8_t in1, uint64_t in2,
double *out0, uint32_t *out1);

View File

@ -12,4 +12,5 @@ Cryptol Reference Manual
OverloadedOperations
TypeDeclarations
Modules
FFI

Binary file not shown.

View File

@ -43,7 +43,6 @@
<li class="toctree-l1 current"><a class="current reference internal" href="#">Basic Syntax</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#declarations">Declarations</a></li>
<li class="toctree-l2"><a class="reference internal" href="#type-signatures">Type Signatures</a></li>
<li class="toctree-l2"><a class="reference internal" href="#numeric-constraint-guards">Numeric Constraint Guards</a></li>
<li class="toctree-l2"><a class="reference internal" href="#layout">Layout</a></li>
<li class="toctree-l2"><a class="reference internal" href="#comments">Comments</a></li>
<li class="toctree-l2"><a class="reference internal" href="#identifiers">Identifiers</a></li>
@ -57,6 +56,7 @@
<li class="toctree-l1"><a class="reference internal" href="OverloadedOperations.html">Overloaded Operations</a></li>
<li class="toctree-l1"><a class="reference internal" href="TypeDeclarations.html">Type Declarations</a></li>
<li class="toctree-l1"><a class="reference internal" href="Modules.html">Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="FFI.html">Foreign Function Interface</a></li>
</ul>
</div>
@ -97,40 +97,6 @@
</pre></div>
</div>
</section>
<section id="numeric-constraint-guards">
<h2>Numeric Constraint Guards<a class="headerlink" href="#numeric-constraint-guards" title="Permalink to this heading"></a></h2>
<p>A declaration with a signature can use numeric constraint guards, which are like
normal guards (such as in a multi-branch <cite>if`</cite> expression) except that the
guarding conditions can be numeric constraints. For example:</p>
<div class="highlight-cryptol notranslate"><div class="highlight"><pre><span></span><span class="nf">len</span><span class="w"> </span><span class="kt">:</span><span class="w"> </span><span class="p">{</span><span class="n">n</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="kr">fin</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="ow">=&gt;</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="p">]</span><span class="n">a</span><span class="w"> </span><span class="ow">-&gt;</span><span class="w"> </span><span class="kt">Integer</span><span class="w"></span>
<span class="nf">len</span><span class="w"> </span><span class="n">xs</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="ow">=&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="ow">=&gt;</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="p">(</span><span class="n">drop</span><span class="w"> </span><span class="p">`{</span><span class="mi">1</span><span class="p">}</span><span class="w"> </span><span class="n">xs</span><span class="p">)</span><span class="w"></span>
</pre></div>
</div>
<p>Note that this is importantly different from</p>
<div class="highlight-cryptol notranslate"><div class="highlight"><pre><span></span><span class="nf">len&#39;</span><span class="w"> </span><span class="kt">:</span><span class="w"> </span><span class="p">{</span><span class="n">n</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="kr">fin</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="ow">=&gt;</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="p">]</span><span class="n">a</span><span class="w"> </span><span class="ow">-&gt;</span><span class="w"> </span><span class="kt">Integer</span><span class="w"></span>
<span class="nf">len&#39;</span><span class="w"> </span><span class="n">xs</span><span class="w"> </span><span class="ow">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">`</span><span class="n">n</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="ow">=&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="p">`</span><span class="n">n</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="ow">=&gt;</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="p">(</span><span class="n">drop</span><span class="w"> </span><span class="p">`{</span><span class="mi">1</span><span class="p">}</span><span class="w"> </span><span class="n">xs</span><span class="p">)</span><span class="w"></span>
</pre></div>
</div>
<p>In <cite>len</cite>, the type-checker cannot determine that <cite>n &gt;= 1</cite> which is
required to use the</p>
<div class="highlight-cryptol notranslate"><div class="highlight"><pre><span></span><span class="nf">drop</span><span class="w"> </span><span class="p">`{</span><span class="mi">1</span><span class="p">}</span><span class="w"> </span><span class="n">xs</span><span class="w"></span>
</pre></div>
</div>
<p>since the <cite>if</cite>s condition is only on values, not types.</p>
<p>However, in <cite>len</cite>, the type-checker locally-assumes the constraint <cite>n &gt; 0</cite> in
that constraint-guarded branch and so it can in fact determine that <cite>n &gt;= 1</cite>.</p>
<dl class="simple">
<dt>Requirements:</dt><dd><ul class="simple">
<li><p>Numeric constraint guards only support constraints over numeric literals,
such as <cite>fin</cite>, <cite>&lt;=</cite>, <cite>==</cite>, etc. Type constraint aliases can also be used as
long as they only constraint numeric literals.</p></li>
<li><p>The numeric constraint guards of a declaration must be exhaustive.</p></li>
</ul>
</dd>
</dl>
</section>
<section id="layout">
<h2>Layout<a class="headerlink" href="#layout" title="Permalink to this heading"></a></h2>
<p>Groups of declarations are organized based on indentation.

View File

@ -55,6 +55,7 @@
<li class="toctree-l1"><a class="reference internal" href="OverloadedOperations.html">Overloaded Operations</a></li>
<li class="toctree-l1"><a class="reference internal" href="TypeDeclarations.html">Type Declarations</a></li>
<li class="toctree-l1"><a class="reference internal" href="Modules.html">Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="FFI.html">Foreign Function Interface</a></li>
</ul>
</div>

View File

@ -57,6 +57,7 @@
<li class="toctree-l1"><a class="reference internal" href="OverloadedOperations.html">Overloaded Operations</a></li>
<li class="toctree-l1"><a class="reference internal" href="TypeDeclarations.html">Type Declarations</a></li>
<li class="toctree-l1"><a class="reference internal" href="Modules.html">Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="FFI.html">Foreign Function Interface</a></li>
</ul>
</div>

View File

@ -0,0 +1,519 @@
<!DOCTYPE html>
<html class="writer-html5" lang="en" >
<head>
<meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Foreign Function Interface &mdash; Cryptol 2.11.0 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="prev" title="Modules" href="Modules.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="RefMan.html" class="icon icon-home"> Cryptol
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<p class="caption" role="heading"><span class="caption-text">Cryptol Reference Manual</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="BasicSyntax.html">Basic Syntax</a></li>
<li class="toctree-l1"><a class="reference internal" href="Expressions.html">Expressions</a></li>
<li class="toctree-l1"><a class="reference internal" href="BasicTypes.html">Basic Types</a></li>
<li class="toctree-l1"><a class="reference internal" href="OverloadedOperations.html">Overloaded Operations</a></li>
<li class="toctree-l1"><a class="reference internal" href="TypeDeclarations.html">Type Declarations</a></li>
<li class="toctree-l1"><a class="reference internal" href="Modules.html">Modules</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">Foreign Function Interface</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#platform-support">Platform support</a></li>
<li class="toctree-l2"><a class="reference internal" href="#basic-usage">Basic usage</a></li>
<li class="toctree-l2"><a class="reference internal" href="#compiling-c-code">Compiling C code</a></li>
<li class="toctree-l2"><a class="reference internal" href="#converting-between-cryptol-and-c-types">Converting between Cryptol and C types</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#overall-structure">Overall structure</a></li>
<li class="toctree-l3"><a class="reference internal" href="#type-parameters">Type parameters</a></li>
<li class="toctree-l3"><a class="reference internal" href="#bit">Bit</a></li>
<li class="toctree-l3"><a class="reference internal" href="#integral-types">Integral types</a></li>
<li class="toctree-l3"><a class="reference internal" href="#floating-point-types">Floating point types</a></li>
<li class="toctree-l3"><a class="reference internal" href="#sequences">Sequences</a></li>
<li class="toctree-l3"><a class="reference internal" href="#tuples-and-records">Tuples and records</a></li>
<li class="toctree-l3"><a class="reference internal" href="#type-synonyms">Type synonyms</a></li>
<li class="toctree-l3"><a class="reference internal" href="#return-values">Return values</a></li>
<li class="toctree-l3"><a class="reference internal" href="#quick-reference">Quick reference</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="#memory">Memory</a></li>
<li class="toctree-l2"><a class="reference internal" href="#evaluation">Evaluation</a></li>
<li class="toctree-l2"><a class="reference internal" href="#example">Example</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="RefMan.html">Cryptol</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="Page navigation">
<ul class="wy-breadcrumbs">
<li><a href="RefMan.html" class="icon icon-home"></a> &raquo;</li>
<li>Foreign Function Interface</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/FFI.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="foreign-function-interface">
<h1>Foreign Function Interface<a class="headerlink" href="#foreign-function-interface" title="Permalink to this heading"></a></h1>
<p>The foreign function interface (FFI) allows Cryptol to call functions written in
C (or other languages that use the C calling convention).</p>
<section id="platform-support">
<h2>Platform support<a class="headerlink" href="#platform-support" title="Permalink to this heading"></a></h2>
<p>The FFI is currently <strong>not supported on Windows</strong>, and only works on Unix-like
systems (macOS and Linux).</p>
</section>
<section id="basic-usage">
<h2>Basic usage<a class="headerlink" href="#basic-usage" title="Permalink to this heading"></a></h2>
<p>Suppose we want to call the following C function:</p>
<div class="highlight-c notranslate"><div class="highlight"><pre><span></span><span class="kt">uint32_t</span><span class="w"> </span><span class="nf">add</span><span class="p">(</span><span class="kt">uint32_t</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="kt">uint32_t</span><span class="w"> </span><span class="n">y</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">y</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<p>In our Cryptol file, we write a <code class="docutils literal notranslate"><span class="pre">foreign</span></code> declaration with no body:</p>
<div class="highlight-cryptol notranslate"><div class="highlight"><pre><span></span><span class="nf">foreign</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="kt">:</span><span class="w"> </span><span class="p">[</span><span class="mi">32</span><span class="p">]</span><span class="w"> </span><span class="ow">-&gt;</span><span class="w"> </span><span class="p">[</span><span class="mi">32</span><span class="p">]</span><span class="w"> </span><span class="ow">-&gt;</span><span class="w"> </span><span class="p">[</span><span class="mi">32</span><span class="p">]</span><span class="w"></span>
</pre></div>
</div>
<p>The C code must first be compiled into a dynamically loaded shared library. When
Cryptol loads the module containing the <code class="docutils literal notranslate"><span class="pre">foreign</span></code> declaration, it will look
for a shared library in the same directory as the Cryptol module, with the same
name as the Cryptol file but with a different file extension. The exact
extension it uses is platform-specific:</p>
<ul class="simple">
<li><p>On Linux, it looks for the extension <code class="docutils literal notranslate"><span class="pre">.so</span></code>.</p></li>
<li><p>On macOS, it looks for the extension <code class="docutils literal notranslate"><span class="pre">.dylib</span></code>.</p></li>
</ul>
<p>For example, if you are on Linux and your <code class="docutils literal notranslate"><span class="pre">foreign</span></code> declaration is in
<code class="docutils literal notranslate"><span class="pre">Foo.cry</span></code>, Cryptol will dynamically load <code class="docutils literal notranslate"><span class="pre">Foo.so</span></code>. Then for each <code class="docutils literal notranslate"><span class="pre">foreign</span></code>
declaration it will look for a symbol with the same name in the shared library.
So in this case the function we want to call must be bound to the symbol <code class="docutils literal notranslate"><span class="pre">add</span></code>
in the shared library.</p>
<p>Once the module is loaded, the foreign <code class="docutils literal notranslate"><span class="pre">add</span></code> function can be called like any
other Cryptol function. Cryptol automatically converts between Cryptol <code class="docutils literal notranslate"><span class="pre">[32]</span></code>
values and C <code class="docutils literal notranslate"><span class="pre">uint32_t</span></code> values.</p>
<p>The whole process would look something like this:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ cc -fPIC -shared Example.c -o Example.so
$ cryptol
Loading module Cryptol
Cryptol&gt; :l Example.cry
Loading module Cryptol
Loading module Main
Main&gt; add 1 2
0x00000003
</pre></div>
</div>
<p>Note: Since Cryptol currently only accesses the compiled binary and not the C
source, it has no way of checking that the Cryptol function type you declare in
your Cryptol code actually matches the type of the C function. <strong>It is your
responsibility to make sure the types match up</strong>. If they do not then there may
be undefined behavior.</p>
</section>
<section id="compiling-c-code">
<h2>Compiling C code<a class="headerlink" href="#compiling-c-code" title="Permalink to this heading"></a></h2>
<p>Cryptol currently does not handle compilation of C code to shared libraries. For
simple usages, you can do this manually with the following commands:</p>
<ul class="simple">
<li><p>Linux: <code class="docutils literal notranslate"><span class="pre">cc</span> <span class="pre">-fPIC</span> <span class="pre">-shared</span> <span class="pre">Foo.c</span> <span class="pre">-o</span> <span class="pre">Foo.so</span></code></p></li>
<li><p>macOS: <code class="docutils literal notranslate"><span class="pre">cc</span> <span class="pre">-dynamiclib</span> <span class="pre">Foo.c</span> <span class="pre">-o</span> <span class="pre">Foo.dylib</span></code></p></li>
</ul>
</section>
<section id="converting-between-cryptol-and-c-types">
<h2>Converting between Cryptol and C types<a class="headerlink" href="#converting-between-cryptol-and-c-types" title="Permalink to this heading"></a></h2>
<p>This section describes how a given Cryptol function signature maps to a C
function prototype. The FFI only supports a limited set of Cryptol types which
have a clear translation into C.</p>
<section id="overall-structure">
<h3>Overall structure<a class="headerlink" href="#overall-structure" title="Permalink to this heading"></a></h3>
<p>Cryptol <code class="docutils literal notranslate"><span class="pre">foreign</span></code> bindings must be functions. These functions may have
multiple (curried) arguments; they may also be polymorphic, with certain
limitations. That is, the general structure of a <code class="docutils literal notranslate"><span class="pre">foreign</span></code> declaration would
look something like this:</p>
<div class="highlight-cryptol notranslate"><div class="highlight"><pre><span></span><span class="nf">foreign</span><span class="w"> </span><span class="n">f</span><span class="w"> </span><span class="kt">:</span><span class="w"> </span><span class="p">{</span><span class="n">a1</span><span class="p">,</span><span class="w"> </span><span class="o">...</span><span class="p">,</span><span class="w"> </span><span class="n">ak</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="n">c1</span><span class="p">,</span><span class="w"> </span><span class="o">...</span><span class="p">,</span><span class="w"> </span><span class="n">cn</span><span class="p">)</span><span class="w"> </span><span class="ow">=&gt;</span><span class="w"> </span><span class="kt">T1</span><span class="w"> </span><span class="ow">-&gt;</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="ow">-&gt;</span><span class="w"> </span><span class="kt">Tm</span><span class="w"> </span><span class="ow">-&gt;</span><span class="w"> </span><span class="kt">Tr</span><span class="w"></span>
</pre></div>
</div>
<p>Each type argument to the Cryptol function (<code class="docutils literal notranslate"><span class="pre">a1,</span> <span class="pre">...,</span> <span class="pre">ak</span></code> above) corresponds
to a value argument to the C function. These arguments are passed first, in the
order of the type variables declared in the Cryptol signature, before any
Cryptol value arguments.</p>
<p>Each value argument to the Cryptol function (<code class="docutils literal notranslate"><span class="pre">T1,</span> <span class="pre">...,</span> <span class="pre">Tm</span></code> above) corresponds
to a number of value arguments to the C function. That is, a Cryptol value
argument could correspond to zero, one, or many C arguments. The C arguments for
each Cryptol value argument are passed in the order of the Cryptol value
arguments, after any C arguments corresponding to Cryptol type arguments.</p>
<p>The return value of the Cryptol function (<code class="docutils literal notranslate"><span class="pre">Tr</span></code> above) is either obtained by
directly returning from the C function or by passing <em>output arguments</em> to the C
function, depending on the return type. Output arguments are pointers to memory
which can be modified by the C function to store its output values. If output
arguments are used, they are passed last, after the C arguments corresponding to
Cryptol arguments.</p>
<p>The following tables list the C type(s) that each Cryptol type (or kind)
corresponds to.</p>
</section>
<section id="type-parameters">
<h3>Type parameters<a class="headerlink" href="#type-parameters" title="Permalink to this heading"></a></h3>
<table class="docutils align-default">
<colgroup>
<col style="width: 55%" />
<col style="width: 45%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Cryptol kind</p></th>
<th class="head"><p>C type</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">#</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">size_t</span></code></p></td>
</tr>
</tbody>
</table>
<p>Only numeric type parameters are allowed in polymorphic <code class="docutils literal notranslate"><span class="pre">foreign</span></code> functions.
Furthermore, each type parameter <code class="docutils literal notranslate"><span class="pre">n</span></code> must satisfy <code class="docutils literal notranslate"><span class="pre">fin</span> <span class="pre">n</span></code>. This has to be
explicitly declared in the Cryptol signature.</p>
<p>Note that if a polymorphic foreign function is called with a type argument that
does not fit in a <code class="docutils literal notranslate"><span class="pre">size_t</span></code>, there will be a runtime error. (While we could
check this statically by requiring that all type variables in foreign functions
satisfy <code class="docutils literal notranslate"><span class="pre">&lt;</span> <span class="pre">2^^64</span></code> instead of just <code class="docutils literal notranslate"><span class="pre">fin</span></code>, in practice this would be too
cumbersome.)</p>
</section>
<section id="bit">
<h3>Bit<a class="headerlink" href="#bit" title="Permalink to this heading"></a></h3>
<table class="docutils align-default">
<colgroup>
<col style="width: 52%" />
<col style="width: 48%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Cryptol type</p></th>
<th class="head"><p>C type</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">Bit</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint8_t</span></code></p></td>
</tr>
</tbody>
</table>
<p>When converting to C, <code class="docutils literal notranslate"><span class="pre">True</span></code> is converted to <code class="docutils literal notranslate"><span class="pre">1</span></code> and <code class="docutils literal notranslate"><span class="pre">False</span></code> to <code class="docutils literal notranslate"><span class="pre">0</span></code>.
When converting to Cryptol, any nonzero number is converted to <code class="docutils literal notranslate"><span class="pre">True</span></code> and
<code class="docutils literal notranslate"><span class="pre">0</span></code> is converted to <code class="docutils literal notranslate"><span class="pre">False</span></code>.</p>
</section>
<section id="integral-types">
<h3>Integral types<a class="headerlink" href="#integral-types" title="Permalink to this heading"></a></h3>
<p>Let <code class="docutils literal notranslate"><span class="pre">K</span> <span class="pre">:</span> <span class="pre">#</span></code> be a Cryptol type. Note <code class="docutils literal notranslate"><span class="pre">K</span></code> must be an actual fixed numeric type
and not a type variable.</p>
<table class="docutils align-default">
<colgroup>
<col style="width: 74%" />
<col style="width: 26%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Cryptol type</p></th>
<th class="head"><p>C type</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">[K]Bit</span></code> where <code class="docutils literal notranslate"><span class="pre">0</span>&#160; <span class="pre">&lt;=</span> <span class="pre">K</span> <span class="pre">&lt;=</span> <span class="pre">8</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint8_t</span></code></p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">[K]Bit</span></code> where <code class="docutils literal notranslate"><span class="pre">8</span>&#160; <span class="pre">&lt;</span>&#160; <span class="pre">K</span> <span class="pre">&lt;=</span> <span class="pre">16</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint16_t</span></code></p></td>
</tr>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">[K]Bit</span></code> where <code class="docutils literal notranslate"><span class="pre">16</span> <span class="pre">&lt;</span>&#160; <span class="pre">K</span> <span class="pre">&lt;=</span> <span class="pre">32</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint32_t</span></code></p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">[K]Bit</span></code> where <code class="docutils literal notranslate"><span class="pre">32</span> <span class="pre">&lt;</span>&#160; <span class="pre">K</span> <span class="pre">&lt;=</span> <span class="pre">64</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint64_t</span></code></p></td>
</tr>
</tbody>
</table>
<p>If the Cryptol type is smaller than the C type, then when converting to C the
value is padded with zero bits, and when converting to Cryptol the extra bits
are ignored. For instance, for the Cryptol type <code class="docutils literal notranslate"><span class="pre">[4]</span></code>, the Cryptol value <code class="docutils literal notranslate"><span class="pre">0xf</span>
<span class="pre">:</span> <span class="pre">[4]</span></code> is converted to the C value <code class="docutils literal notranslate"><span class="pre">uint8_t</span></code> <code class="docutils literal notranslate"><span class="pre">0x0f</span></code>, and the C <code class="docutils literal notranslate"><span class="pre">uint8_t</span></code>
<code class="docutils literal notranslate"><span class="pre">0xaf</span></code> is converted to the Cryptol value <code class="docutils literal notranslate"><span class="pre">0xf</span> <span class="pre">:</span> <span class="pre">[4]</span></code>.</p>
<p>Note that words larger than 64 bits are not supported, since there is no
standard C integral type for that. You can split it into a sequence of smaller
words first in Cryptol, then use the FFI conversion for sequences of words to
handle it in C as an array.</p>
</section>
<section id="floating-point-types">
<h3>Floating point types<a class="headerlink" href="#floating-point-types" title="Permalink to this heading"></a></h3>
<table class="docutils align-default">
<colgroup>
<col style="width: 55%" />
<col style="width: 45%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Cryptol type</p></th>
<th class="head"><p>C type</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">Float32</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">float</span></code></p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">Float64</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">double</span></code></p></td>
</tr>
</tbody>
</table>
<p>Note: the Cryptol <code class="docutils literal notranslate"><span class="pre">Float</span></code> types are defined in the built-in module <code class="docutils literal notranslate"><span class="pre">Float</span></code>.
Other sizes of floating points are not supported.</p>
</section>
<section id="sequences">
<h3>Sequences<a class="headerlink" href="#sequences" title="Permalink to this heading"></a></h3>
<p>Let <code class="docutils literal notranslate"><span class="pre">n</span> <span class="pre">:</span> <span class="pre">#</span></code> be a Cryptol type, possibly containing type variables, that
satisfies <code class="docutils literal notranslate"><span class="pre">fin</span> <span class="pre">n</span></code>, and <code class="docutils literal notranslate"><span class="pre">T</span></code> be one of the above Cryptol <em>integral types</em> or
<em>floating point types</em>. Let <code class="docutils literal notranslate"><span class="pre">U</span></code> be the C type that <code class="docutils literal notranslate"><span class="pre">T</span></code> corresponds to.</p>
<table class="docutils align-default">
<colgroup>
<col style="width: 52%" />
<col style="width: 48%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Cryptol type</p></th>
<th class="head"><p>C type</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">[n]T</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">U*</span></code></p></td>
</tr>
</tbody>
</table>
<p>The C pointer points to an array of <code class="docutils literal notranslate"><span class="pre">n</span></code> elements of type <code class="docutils literal notranslate"><span class="pre">U</span></code>. Note that,
while the length of the array itself is not explicitly passed along with the
pointer, any type arguments contained in the size are passed as C <code class="docutils literal notranslate"><span class="pre">size_t</span></code>s
earlier, so the C code can always know the length of the array.</p>
</section>
<section id="tuples-and-records">
<h3>Tuples and records<a class="headerlink" href="#tuples-and-records" title="Permalink to this heading"></a></h3>
<p>Let <code class="docutils literal notranslate"><span class="pre">T1,</span> <span class="pre">T2,</span> <span class="pre">...,</span> <span class="pre">Tn</span></code> be Cryptol types supported by the FFI (which may be any
of the types mentioned above, or tuples and records themselves). Let
<code class="docutils literal notranslate"><span class="pre">U1,</span> <span class="pre">U2,</span> <span class="pre">...,</span> <span class="pre">Un</span></code> be the C types that <code class="docutils literal notranslate"><span class="pre">T1,</span> <span class="pre">T2,</span> <span class="pre">...,</span> <span class="pre">Tn</span></code> respectively
correspond to. Let <code class="docutils literal notranslate"><span class="pre">f1,</span> <span class="pre">f2,</span> <span class="pre">...,</span> <span class="pre">fn</span></code> be arbitrary field names.</p>
<table class="docutils align-default">
<colgroup>
<col style="width: 63%" />
<col style="width: 37%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Cryptol type</p></th>
<th class="head"><p>C types</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">(T1,</span> <span class="pre">T2,</span> <span class="pre">...,</span> <span class="pre">Tn)</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">U1,</span> <span class="pre">U2,</span> <span class="pre">...,</span> <span class="pre">Un</span></code></p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">{f1:</span> <span class="pre">T1,</span> <span class="pre">f2:</span> <span class="pre">T2,</span> <span class="pre">...,</span> <span class="pre">fn:</span> <span class="pre">Tn}</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">U1,</span> <span class="pre">U2,</span> <span class="pre">...,</span> <span class="pre">Un</span></code></p></td>
</tr>
</tbody>
</table>
<p>In this case, each Cryptol tuple or record is flattened out; passing a tuple as
an argument behaves the same as if you passed its components individually. This
flattening is applied recursively for nested tuples and records. Note that this
means empty tuples dont map to any C values at all.</p>
</section>
<section id="type-synonyms">
<h3>Type synonyms<a class="headerlink" href="#type-synonyms" title="Permalink to this heading"></a></h3>
<p>All type synonyms are expanded before applying the above rules, so you can use
type synonyms in <code class="docutils literal notranslate"><span class="pre">foreign</span></code> declarations to improve readability.</p>
</section>
<section id="return-values">
<h3>Return values<a class="headerlink" href="#return-values" title="Permalink to this heading"></a></h3>
<p>If the Cryptol return type is <code class="docutils literal notranslate"><span class="pre">Bit</span></code> or one of the above <em>integral types</em> or
<em>floating point types</em>, the value is returned directly from the C function. In
that case, the return type of the C function will be the C type corresponding to
the Cryptol type, and no extra arguments are added.</p>
<p>If the Cryptol return type is a sequence, tuple, or record, then the value is
returned using output arguments, and the return type of the C function will be
<code class="docutils literal notranslate"><span class="pre">void</span></code>. For tuples and records, each component is recursively returned as
output arguments. When treated as an output argument, each C type <code class="docutils literal notranslate"><span class="pre">U</span></code> will be
a pointer <code class="docutils literal notranslate"><span class="pre">U*</span></code> instead, except in the case of sequences, where the output and
input versions are the same type, because it is already a pointer.</p>
</section>
<section id="quick-reference">
<h3>Quick reference<a class="headerlink" href="#quick-reference" title="Permalink to this heading"></a></h3>
<table class="docutils align-default">
<colgroup>
<col style="width: 37%" />
<col style="width: 21%" />
<col style="width: 14%" />
<col style="width: 27%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Cryptol type (or kind)</p></th>
<th class="head"><p>C argument type(s)</p></th>
<th class="head"><p>C return type</p></th>
<th class="head"><p>C output argument type(s)</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">#</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">size_t</span></code></p></td>
<td><p>N/A</p></td>
<td><p>N/A</p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">Bit</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint8_t</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint8_t</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint8_t*</span></code></p></td>
</tr>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">[K]Bit</span></code> where <code class="docutils literal notranslate"><span class="pre">0</span>&#160; <span class="pre">&lt;=</span> <span class="pre">K</span> <span class="pre">&lt;=</span> <span class="pre">8</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint8_t</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint8_t</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint8_t*</span></code></p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">[K]Bit</span></code> where <code class="docutils literal notranslate"><span class="pre">8</span>&#160; <span class="pre">&lt;</span>&#160; <span class="pre">K</span> <span class="pre">&lt;=</span> <span class="pre">16</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint16_t</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint16_t</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint16_t*</span></code></p></td>
</tr>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">[K]Bit</span></code> where <code class="docutils literal notranslate"><span class="pre">16</span> <span class="pre">&lt;</span>&#160; <span class="pre">K</span> <span class="pre">&lt;=</span> <span class="pre">32</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint32_t</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint32_t</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint32_t*</span></code></p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">[K]Bit</span></code> where <code class="docutils literal notranslate"><span class="pre">32</span> <span class="pre">&lt;</span>&#160; <span class="pre">K</span> <span class="pre">&lt;=</span> <span class="pre">64</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint64_t</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint64_t</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">uint64_t*</span></code></p></td>
</tr>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">Float32</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">float</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">float</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">float*</span></code></p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">Float64</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">double</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">double</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">double*</span></code></p></td>
</tr>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">[n]T</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">U*</span></code></p></td>
<td><p>N/A</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">U*</span></code></p></td>
</tr>
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">(T1,</span> <span class="pre">T2,</span> <span class="pre">...,</span> <span class="pre">Tn)</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">U1,</span> <span class="pre">U2,</span> <span class="pre">...,</span> <span class="pre">Un</span></code></p></td>
<td><p>N/A</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">V1,</span> <span class="pre">V2,</span> <span class="pre">...,</span> <span class="pre">Vn</span></code></p></td>
</tr>
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">{f1:</span> <span class="pre">T1,</span> <span class="pre">f2:</span> <span class="pre">T2,</span> <span class="pre">...,</span> <span class="pre">fn:</span> <span class="pre">Tn}</span></code></p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">U1,</span> <span class="pre">U2,</span> <span class="pre">...,</span> <span class="pre">Un</span></code></p></td>
<td><p>N/A</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">V1,</span> <span class="pre">V2,</span> <span class="pre">...,</span> <span class="pre">Vn</span></code></p></td>
</tr>
</tbody>
</table>
<p>where <code class="docutils literal notranslate"><span class="pre">K</span></code> is a constant number, <code class="docutils literal notranslate"><span class="pre">n</span></code> is a variable number, <code class="docutils literal notranslate"><span class="pre">Ti</span></code> is a type,
<code class="docutils literal notranslate"><span class="pre">Ui</span></code> is its C argument type, and <code class="docutils literal notranslate"><span class="pre">Vi</span></code> is its C output argument type.</p>
</section>
</section>
<section id="memory">
<h2>Memory<a class="headerlink" href="#memory" title="Permalink to this heading"></a></h2>
<p>When pointers are involved, namely in the cases of sequences and output
arguments, they point to memory. This memory is always allocated and deallocated
by Cryptol; the C code does not need to manage this memory.</p>
<p>In the case of sequences, the pointer will point to an array. In the case of an
output argument for a non-sequence type, the pointer will point to a piece of
memory large enough to hold the given C type, and you should not try to access
any adjacent memory.</p>
<p>For input sequence arguments, the array will already be set to the values
corresponding to the Cryptol values in the sequence. For output arguments, the
memory may be uninitialized when passed to C, and the C code should not read
from it. It must write to the memory the value it is returning.</p>
</section>
<section id="evaluation">
<h2>Evaluation<a class="headerlink" href="#evaluation" title="Permalink to this heading"></a></h2>
<p>All Cryptol arguments are fully evaluated when a foreign function is called.</p>
</section>
<section id="example">
<h2>Example<a class="headerlink" href="#example" title="Permalink to this heading"></a></h2>
<p>The Cryptol signature</p>
<div class="highlight-cryptol notranslate"><div class="highlight"><pre><span></span><span class="nf">foreign</span><span class="w"> </span><span class="n">f</span><span class="w"> </span><span class="kt">:</span><span class="w"> </span><span class="p">{</span><span class="n">n</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="kr">fin</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="ow">=&gt;</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="p">][</span><span class="mi">10</span><span class="p">]</span><span class="w"> </span><span class="ow">-&gt;</span><span class="w"> </span><span class="p">{</span><span class="n">a</span><span class="w"> </span><span class="kt">:</span><span class="w"> </span><span class="kr">Bit</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="kt">:</span><span class="w"> </span><span class="p">[</span><span class="mi">64</span><span class="p">]}</span><span class="w"></span>
<span class="w"> </span><span class="ow">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="kt">Float64</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">][</span><span class="mi">20</span><span class="p">])</span><span class="w"></span>
</pre></div>
</div>
<p>corresponds to the C signature</p>
<div class="highlight-c notranslate"><div class="highlight"><pre><span></span><span class="kt">void</span><span class="w"> </span><span class="nf">f</span><span class="p">(</span><span class="kt">size_t</span><span class="w"> </span><span class="n">n</span><span class="p">,</span><span class="w"> </span><span class="kt">uint16_t</span><span class="w"> </span><span class="o">*</span><span class="n">in0</span><span class="p">,</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">in1</span><span class="p">,</span><span class="w"> </span><span class="kt">uint64_t</span><span class="w"> </span><span class="n">in2</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="kt">double</span><span class="w"> </span><span class="o">*</span><span class="n">out0</span><span class="p">,</span><span class="w"> </span><span class="kt">uint32_t</span><span class="w"> </span><span class="o">*</span><span class="n">out1</span><span class="p">);</span><span class="w"></span>
</pre></div>
</div>
</section>
</section>
</div>
</div>
<footer><div class="rst-footer-buttons" role="navigation" aria-label="Footer">
<a href="Modules.html" class="btn btn-neutral float-left" title="Modules" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> Previous</a>
</div>
<hr/>
<div role="contentinfo">
<p>&#169; Copyright 2021, The Cryptol Team.</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script>
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>

View File

@ -19,6 +19,7 @@
<script src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Foreign Function Interface" href="FFI.html" />
<link rel="prev" title="Type Declarations" href="TypeDeclarations.html" />
</head>
@ -71,6 +72,7 @@
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="FFI.html">Foreign Function Interface</a></li>
</ul>
</div>
@ -766,6 +768,7 @@ in an instantiation. Here is an example:</p>
</div>
<footer><div class="rst-footer-buttons" role="navigation" aria-label="Footer">
<a href="TypeDeclarations.html" class="btn btn-neutral float-left" title="Type Declarations" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> Previous</a>
<a href="FFI.html" class="btn btn-neutral float-right" title="Foreign Function Interface" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
</div>
<hr/>

View File

@ -57,6 +57,7 @@
</li>
<li class="toctree-l1"><a class="reference internal" href="TypeDeclarations.html">Type Declarations</a></li>
<li class="toctree-l1"><a class="reference internal" href="Modules.html">Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="FFI.html">Foreign Function Interface</a></li>
</ul>
</div>

View File

@ -45,6 +45,7 @@
<li class="toctree-l1"><a class="reference internal" href="OverloadedOperations.html">Overloaded Operations</a></li>
<li class="toctree-l1"><a class="reference internal" href="TypeDeclarations.html">Type Declarations</a></li>
<li class="toctree-l1"><a class="reference internal" href="Modules.html">Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="FFI.html">Foreign Function Interface</a></li>
</ul>
</div>
@ -154,6 +155,28 @@
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="FFI.html">Foreign Function Interface</a><ul>
<li class="toctree-l2"><a class="reference internal" href="FFI.html#platform-support">Platform support</a></li>
<li class="toctree-l2"><a class="reference internal" href="FFI.html#basic-usage">Basic usage</a></li>
<li class="toctree-l2"><a class="reference internal" href="FFI.html#compiling-c-code">Compiling C code</a></li>
<li class="toctree-l2"><a class="reference internal" href="FFI.html#converting-between-cryptol-and-c-types">Converting between Cryptol and C types</a><ul>
<li class="toctree-l3"><a class="reference internal" href="FFI.html#overall-structure">Overall structure</a></li>
<li class="toctree-l3"><a class="reference internal" href="FFI.html#type-parameters">Type parameters</a></li>
<li class="toctree-l3"><a class="reference internal" href="FFI.html#bit">Bit</a></li>
<li class="toctree-l3"><a class="reference internal" href="FFI.html#integral-types">Integral types</a></li>
<li class="toctree-l3"><a class="reference internal" href="FFI.html#floating-point-types">Floating point types</a></li>
<li class="toctree-l3"><a class="reference internal" href="FFI.html#sequences">Sequences</a></li>
<li class="toctree-l3"><a class="reference internal" href="FFI.html#tuples-and-records">Tuples and records</a></li>
<li class="toctree-l3"><a class="reference internal" href="FFI.html#type-synonyms">Type synonyms</a></li>
<li class="toctree-l3"><a class="reference internal" href="FFI.html#return-values">Return values</a></li>
<li class="toctree-l3"><a class="reference internal" href="FFI.html#quick-reference">Quick reference</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="FFI.html#memory">Memory</a></li>
<li class="toctree-l2"><a class="reference internal" href="FFI.html#evaluation">Evaluation</a></li>
<li class="toctree-l2"><a class="reference internal" href="FFI.html#example">Example</a></li>
</ul>
</li>
</ul>
</div>
</section>

View File

@ -50,6 +50,7 @@
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="Modules.html">Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="FFI.html">Foreign Function Interface</a></li>
</ul>
</div>

View File

@ -0,0 +1,306 @@
Foreign Function Interface
==========================
The foreign function interface (FFI) allows Cryptol to call functions written in
C (or other languages that use the C calling convention).
Platform support
----------------
The FFI is currently **not supported on Windows**, and only works on Unix-like
systems (macOS and Linux).
Basic usage
-----------
Suppose we want to call the following C function:
.. code-block:: c
uint32_t add(uint32_t x, uint32_t y) {
return x + y;
}
In our Cryptol file, we write a ``foreign`` declaration with no body:
.. code-block:: cryptol
foreign add : [32] -> [32] -> [32]
The C code must first be compiled into a dynamically loaded shared library. When
Cryptol loads the module containing the ``foreign`` declaration, it will look
for a shared library in the same directory as the Cryptol module, with the same
name as the Cryptol file but with a different file extension. The exact
extension it uses is platform-specific:
* On Linux, it looks for the extension ``.so``.
* On macOS, it looks for the extension ``.dylib``.
For example, if you are on Linux and your ``foreign`` declaration is in
``Foo.cry``, Cryptol will dynamically load ``Foo.so``. Then for each ``foreign``
declaration it will look for a symbol with the same name in the shared library.
So in this case the function we want to call must be bound to the symbol ``add``
in the shared library.
Once the module is loaded, the foreign ``add`` function can be called like any
other Cryptol function. Cryptol automatically converts between Cryptol ``[32]``
values and C ``uint32_t`` values.
The whole process would look something like this:
.. code-block::
$ cc -fPIC -shared Example.c -o Example.so
$ cryptol
Loading module Cryptol
Cryptol> :l Example.cry
Loading module Cryptol
Loading module Main
Main> add 1 2
0x00000003
Note: Since Cryptol currently only accesses the compiled binary and not the C
source, it has no way of checking that the Cryptol function type you declare in
your Cryptol code actually matches the type of the C function. **It is your
responsibility to make sure the types match up**. If they do not then there may
be undefined behavior.
Compiling C code
----------------
Cryptol currently does not handle compilation of C code to shared libraries. For
simple usages, you can do this manually with the following commands:
* Linux: ``cc -fPIC -shared Foo.c -o Foo.so``
* macOS: ``cc -dynamiclib Foo.c -o Foo.dylib``
Converting between Cryptol and C types
--------------------------------------
This section describes how a given Cryptol function signature maps to a C
function prototype. The FFI only supports a limited set of Cryptol types which
have a clear translation into C.
Overall structure
~~~~~~~~~~~~~~~~~
Cryptol ``foreign`` bindings must be functions. These functions may have
multiple (curried) arguments; they may also be polymorphic, with certain
limitations. That is, the general structure of a ``foreign`` declaration would
look something like this:
.. code-block:: cryptol
foreign f : {a1, ..., ak} (c1, ..., cn) => T1 -> ... -> Tm -> Tr
Each type argument to the Cryptol function (``a1, ..., ak`` above) corresponds
to a value argument to the C function. These arguments are passed first, in the
order of the type variables declared in the Cryptol signature, before any
Cryptol value arguments.
Each value argument to the Cryptol function (``T1, ..., Tm`` above) corresponds
to a number of value arguments to the C function. That is, a Cryptol value
argument could correspond to zero, one, or many C arguments. The C arguments for
each Cryptol value argument are passed in the order of the Cryptol value
arguments, after any C arguments corresponding to Cryptol type arguments.
The return value of the Cryptol function (``Tr`` above) is either obtained by
directly returning from the C function or by passing *output arguments* to the C
function, depending on the return type. Output arguments are pointers to memory
which can be modified by the C function to store its output values. If output
arguments are used, they are passed last, after the C arguments corresponding to
Cryptol arguments.
The following tables list the C type(s) that each Cryptol type (or kind)
corresponds to.
Type parameters
~~~~~~~~~~~~~~~
============ ==========
Cryptol kind C type
============ ==========
``#`` ``size_t``
============ ==========
Only numeric type parameters are allowed in polymorphic ``foreign`` functions.
Furthermore, each type parameter ``n`` must satisfy ``fin n``. This has to be
explicitly declared in the Cryptol signature.
Note that if a polymorphic foreign function is called with a type argument that
does not fit in a ``size_t``, there will be a runtime error. (While we could
check this statically by requiring that all type variables in foreign functions
satisfy ``< 2^^64`` instead of just ``fin``, in practice this would be too
cumbersome.)
Bit
~~~
============ ===========
Cryptol type C type
============ ===========
``Bit`` ``uint8_t``
============ ===========
When converting to C, ``True`` is converted to ``1`` and ``False`` to ``0``.
When converting to Cryptol, any nonzero number is converted to ``True`` and
``0`` is converted to ``False``.
Integral types
~~~~~~~~~~~~~~
Let ``K : #`` be a Cryptol type. Note ``K`` must be an actual fixed numeric type
and not a type variable.
================================== ============
Cryptol type C type
================================== ============
``[K]Bit`` where ``0 <= K <= 8`` ``uint8_t``
``[K]Bit`` where ``8 < K <= 16`` ``uint16_t``
``[K]Bit`` where ``16 < K <= 32`` ``uint32_t``
``[K]Bit`` where ``32 < K <= 64`` ``uint64_t``
================================== ============
If the Cryptol type is smaller than the C type, then when converting to C the
value is padded with zero bits, and when converting to Cryptol the extra bits
are ignored. For instance, for the Cryptol type ``[4]``, the Cryptol value ``0xf
: [4]`` is converted to the C value ``uint8_t`` ``0x0f``, and the C ``uint8_t``
``0xaf`` is converted to the Cryptol value ``0xf : [4]``.
Note that words larger than 64 bits are not supported, since there is no
standard C integral type for that. You can split it into a sequence of smaller
words first in Cryptol, then use the FFI conversion for sequences of words to
handle it in C as an array.
Floating point types
~~~~~~~~~~~~~~~~~~~~
============ ==========
Cryptol type C type
============ ==========
``Float32`` ``float``
``Float64`` ``double``
============ ==========
Note: the Cryptol ``Float`` types are defined in the built-in module ``Float``.
Other sizes of floating points are not supported.
Sequences
~~~~~~~~~
Let ``n : #`` be a Cryptol type, possibly containing type variables, that
satisfies ``fin n``, and ``T`` be one of the above Cryptol *integral types* or
*floating point types*. Let ``U`` be the C type that ``T`` corresponds to.
============ ===========
Cryptol type C type
============ ===========
``[n]T`` ``U*``
============ ===========
The C pointer points to an array of ``n`` elements of type ``U``. Note that,
while the length of the array itself is not explicitly passed along with the
pointer, any type arguments contained in the size are passed as C ``size_t``'s
earlier, so the C code can always know the length of the array.
Tuples and records
~~~~~~~~~~~~~~~~~~
Let ``T1, T2, ..., Tn`` be Cryptol types supported by the FFI (which may be any
of the types mentioned above, or tuples and records themselves). Let
``U1, U2, ..., Un`` be the C types that ``T1, T2, ..., Tn`` respectively
correspond to. Let ``f1, f2, ..., fn`` be arbitrary field names.
================================= ===================
Cryptol type C types
================================= ===================
``(T1, T2, ..., Tn)`` ``U1, U2, ..., Un``
``{f1: T1, f2: T2, ..., fn: Tn}`` ``U1, U2, ..., Un``
================================= ===================
In this case, each Cryptol tuple or record is flattened out; passing a tuple as
an argument behaves the same as if you passed its components individually. This
flattening is applied recursively for nested tuples and records. Note that this
means empty tuples don't map to any C values at all.
Type synonyms
~~~~~~~~~~~~~
All type synonyms are expanded before applying the above rules, so you can use
type synonyms in ``foreign`` declarations to improve readability.
Return values
~~~~~~~~~~~~~
If the Cryptol return type is ``Bit`` or one of the above *integral types* or
*floating point types*, the value is returned directly from the C function. In
that case, the return type of the C function will be the C type corresponding to
the Cryptol type, and no extra arguments are added.
If the Cryptol return type is a sequence, tuple, or record, then the value is
returned using output arguments, and the return type of the C function will be
``void``. For tuples and records, each component is recursively returned as
output arguments. When treated as an output argument, each C type ``U`` will be
a pointer ``U*`` instead, except in the case of sequences, where the output and
input versions are the same type, because it is already a pointer.
Quick reference
~~~~~~~~~~~~~~~
================================== =================== ============= =========================
Cryptol type (or kind) C argument type(s) C return type C output argument type(s)
================================== =================== ============= =========================
``#`` ``size_t`` N/A N/A
``Bit`` ``uint8_t`` ``uint8_t`` ``uint8_t*``
``[K]Bit`` where ``0 <= K <= 8`` ``uint8_t`` ``uint8_t`` ``uint8_t*``
``[K]Bit`` where ``8 < K <= 16`` ``uint16_t`` ``uint16_t`` ``uint16_t*``
``[K]Bit`` where ``16 < K <= 32`` ``uint32_t`` ``uint32_t`` ``uint32_t*``
``[K]Bit`` where ``32 < K <= 64`` ``uint64_t`` ``uint64_t`` ``uint64_t*``
``Float32`` ``float`` ``float`` ``float*``
``Float64`` ``double`` ``double`` ``double*``
``[n]T`` ``U*`` N/A ``U*``
``(T1, T2, ..., Tn)`` ``U1, U2, ..., Un`` N/A ``V1, V2, ..., Vn``
``{f1: T1, f2: T2, ..., fn: Tn}`` ``U1, U2, ..., Un`` N/A ``V1, V2, ..., Vn``
================================== =================== ============= =========================
where ``K`` is a constant number, ``n`` is a variable number, ``Ti`` is a type,
``Ui`` is its C argument type, and ``Vi`` is its C output argument type.
Memory
------
When pointers are involved, namely in the cases of sequences and output
arguments, they point to memory. This memory is always allocated and deallocated
by Cryptol; the C code does not need to manage this memory.
In the case of sequences, the pointer will point to an array. In the case of an
output argument for a non-sequence type, the pointer will point to a piece of
memory large enough to hold the given C type, and you should not try to access
any adjacent memory.
For input sequence arguments, the array will already be set to the values
corresponding to the Cryptol values in the sequence. For output arguments, the
memory may be uninitialized when passed to C, and the C code should not read
from it. It must write to the memory the value it is returning.
Evaluation
----------
All Cryptol arguments are fully evaluated when a foreign function is called.
Example
-------
The Cryptol signature
.. code-block:: cryptol
foreign f : {n} (fin n) => [n][10] -> {a : Bit, b : [64]}
-> (Float64, [n + 1][20])
corresponds to the C signature
.. code-block:: c
void f(size_t n, uint16_t *in0, uint8_t in1, uint64_t in2,
double *out0, uint32_t *out1);

View File

@ -12,4 +12,5 @@ Cryptol Reference Manual
OverloadedOperations
TypeDeclarations
Modules
FFI

View File

@ -236,7 +236,6 @@ div.body p, div.body dd, div.body li, div.body blockquote {
a.headerlink {
visibility: hidden;
}
a.brackets:before,
span.brackets > a:before{
content: "[";
@ -247,6 +246,7 @@ span.brackets > a:after {
content: "]";
}
h1:hover > a.headerlink,
h2:hover > a.headerlink,
h3:hover > a.headerlink,
@ -334,14 +334,12 @@ aside.sidebar {
p.sidebar-title {
font-weight: bold;
}
div.admonition, div.topic, aside.topic, blockquote {
div.admonition, div.topic, blockquote {
clear: left;
}
/* -- topics ---------------------------------------------------------------- */
div.topic, aside.topic {
div.topic {
border: 1px solid #ccc;
padding: 7px;
margin: 10px 0 10px 0;
@ -380,7 +378,6 @@ div.body p.centered {
div.sidebar > :last-child,
aside.sidebar > :last-child,
div.topic > :last-child,
aside.topic > :last-child,
div.admonition > :last-child {
margin-bottom: 0;
}
@ -388,7 +385,6 @@ div.admonition > :last-child {
div.sidebar::after,
aside.sidebar::after,
div.topic::after,
aside.topic::after,
div.admonition::after,
blockquote::after {
display: block;
@ -612,8 +608,6 @@ ol.simple p,
ul.simple p {
margin-bottom: 0;
}
/* Docutils 0.17 and older (footnotes & citations) */
dl.footnote > dt,
dl.citation > dt {
float: left;
@ -631,33 +625,6 @@ dl.citation > dd:after {
clear: both;
}
/* Docutils 0.18+ (footnotes & citations) */
aside.footnote > span,
div.citation > span {
float: left;
}
aside.footnote > span:last-of-type,
div.citation > span:last-of-type {
padding-right: 0.5em;
}
aside.footnote > p {
margin-left: 2em;
}
div.citation > p {
margin-left: 4em;
}
aside.footnote > p:last-of-type,
div.citation > p:last-of-type {
margin-bottom: 0em;
}
aside.footnote > p:last-of-type:after,
div.citation > p:last-of-type:after {
content: "";
clear: both;
}
/* Footnotes & citations ends */
dl.field-list {
display: grid;
grid-template-columns: fit-content(30%) auto;
@ -669,11 +636,11 @@ dl.field-list > dt {
padding-left: 0.5em;
padding-right: 5px;
}
dl.field-list > dt:after {
content: ":";
}
dl.field-list > dd {
padding-left: 0.5em;
margin-top: 0em;

View File

@ -10,5 +10,5 @@ var DOCUMENTATION_OPTIONS = {
SOURCELINK_SUFFIX: '.txt',
NAVIGATION_WITH_KEYS: false,
SHOW_SEARCH_SUMMARY: true,
ENABLE_SEARCH_SHORTCUTS: false,
ENABLE_SEARCH_SHORTCUTS: true,
};

View File

@ -88,7 +88,7 @@ const _displayItem = (item, highlightTerms, searchTerms) => {
linkEl.href = linkUrl + "?" + params.toString() + anchor;
linkEl.innerHTML = title;
if (descr)
listItem.appendChild(document.createElement("span")).innerText =
listItem.appendChild(document.createElement("span")).innerHTML =
" (" + descr + ")";
else if (showSearchSummary)
fetch(requestUrl)
@ -155,10 +155,8 @@ const Search = {
_pulse_status: -1,
htmlToText: (htmlString) => {
const htmlElement = document
.createRange()
.createContextualFragment(htmlString);
_removeChildren(htmlElement.querySelectorAll(".headerlink"));
const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html');
htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() });
const docContent = htmlElement.querySelector('[role="main"]');
if (docContent !== undefined) return docContent.textContent;
console.warn(
@ -504,11 +502,12 @@ const Search = {
* latter for highlighting it.
*/
makeSearchSummary: (htmlText, keywords, highlightWords) => {
const text = Search.htmlToText(htmlText).toLowerCase();
const text = Search.htmlToText(htmlText);
if (text === "") return null;
const textLower = text.toLowerCase();
const actualStartPosition = [...keywords]
.map((k) => text.indexOf(k.toLowerCase()))
.map((k) => textLower.indexOf(k.toLowerCase()))
.filter((i) => i > -1)
.slice(-1)[0];
const startWithContext = Math.max(actualStartPosition - 120, 0);
@ -516,9 +515,9 @@ const Search = {
const top = startWithContext === 0 ? "" : "...";
const tail = startWithContext + 240 < text.length ? "..." : "";
let summary = document.createElement("div");
let summary = document.createElement("p");
summary.classList.add("context");
summary.innerText = top + text.substr(startWithContext, 240).trim() + tail;
summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;
highlightWords.forEach((highlightWord) =>
_highlightText(summary, highlightWord, "highlighted")

View File

@ -43,6 +43,7 @@
<li class="toctree-l1"><a class="reference internal" href="OverloadedOperations.html">Overloaded Operations</a></li>
<li class="toctree-l1"><a class="reference internal" href="TypeDeclarations.html">Type Declarations</a></li>
<li class="toctree-l1"><a class="reference internal" href="Modules.html">Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="FFI.html">Foreign Function Interface</a></li>
</ul>
</div>

Binary file not shown.

View File

@ -46,6 +46,7 @@
<li class="toctree-l1"><a class="reference internal" href="OverloadedOperations.html">Overloaded Operations</a></li>
<li class="toctree-l1"><a class="reference internal" href="TypeDeclarations.html">Type Declarations</a></li>
<li class="toctree-l1"><a class="reference internal" href="Modules.html">Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="FFI.html">Foreign Function Interface</a></li>
</ul>
</div>

File diff suppressed because one or more lines are too long

227
src/Cryptol/Backend/FFI.hs Normal file
View File

@ -0,0 +1,227 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
-- | The implementation of loading and calling external functions from shared
-- libraries. Currently works on Unix only.
module Cryptol.Backend.FFI
( ForeignSrc
, loadForeignSrc
, unloadForeignSrc
#ifdef FFI_ENABLED
, ForeignImpl
, loadForeignImpl
, FFIArg
, FFIRet
, SomeFFIArg (..)
, callForeignImpl
#endif
)
where
import Control.DeepSeq
import Cryptol.Backend.FFI.Error
#ifdef FFI_ENABLED
import Control.Concurrent.MVar
import Control.Exception
import Control.Monad
import Data.Bifunctor
import Data.Word
import Foreign hiding (newForeignPtr)
import Foreign.C.Types
import Foreign.Concurrent
import Foreign.LibFFI
import System.FilePath ((-<.>))
import System.IO.Error
import System.Posix.DynamicLinker
import Cryptol.Utils.Panic
#else
import GHC.Generics
#endif
#ifdef FFI_ENABLED
-- | A source from which we can retrieve implementations of foreign functions.
data ForeignSrc = ForeignSrc
{ -- | The 'ForeignPtr' wraps the pointer returned by 'dlopen', where the
-- finalizer calls 'dlclose' when the library is no longer needed. We keep
-- references to the 'ForeignPtr' in each foreign function that is in the
-- evaluation environment, so that the shared library will stay open as long
-- as there are references to it.
foreignSrcFPtr :: ForeignPtr ()
-- | We support explicit unloading of the shared library so we keep track of
-- if it has already been unloaded, and if so the finalizer does nothing.
-- This is updated atomically when the library is unloaded.
, foreignSrcLoaded :: MVar Bool }
instance Show ForeignSrc where
show = show . foreignSrcFPtr
instance NFData ForeignSrc where
rnf ForeignSrc {..} = foreignSrcFPtr `seq` foreignSrcLoaded `deepseq` ()
-- | Load a 'ForeignSrc' for the given __Cryptol__ file path. The file path of
-- the shared library that we try to load is the same as the Cryptol file path
-- except with a platform specific extension.
loadForeignSrc :: FilePath -> IO (Either FFILoadError ForeignSrc)
loadForeignSrc = loadForeignLib >=> traverse \ptr -> do
foreignSrcLoaded <- newMVar True
foreignSrcFPtr <- newForeignPtr ptr (unloadForeignSrc' foreignSrcLoaded ptr)
pure ForeignSrc {..}
loadForeignLib :: FilePath -> IO (Either FFILoadError (Ptr ()))
#ifdef darwin_HOST_OS
-- On Darwin, try loading .dylib first, and if that fails try .so
loadForeignLib path =
(Right <$> open "dylib") `catchIOError` \e1 ->
(Right <$> open "so") `catchIOError` \e2 ->
pure $ Left $ CantLoadFFISrc path $
displayException e1 ++ "\n" ++ displayException e2
#else
-- On other platforms, use .so
loadForeignLib path =
tryLoad (CantLoadFFISrc path) $ open "so"
#endif
where -- RTLD_NOW so we can make sure that the symbols actually exist at
-- module loading time
open ext = undl <$> dlopen (path -<.> ext) [RTLD_NOW]
-- | Explicitly unload a 'ForeignSrc' immediately instead of waiting for the
-- garbage collector to do it. This can be useful if you want to immediately
-- load the same library again to pick up new changes.
--
-- The 'ForeignSrc' __must not__ be used in any way after this is called,
-- including calling 'ForeignImpl's loaded from it.
unloadForeignSrc :: ForeignSrc -> IO ()
unloadForeignSrc ForeignSrc {..} = withForeignPtr foreignSrcFPtr $
unloadForeignSrc' foreignSrcLoaded
unloadForeignSrc' :: MVar Bool -> Ptr () -> IO ()
unloadForeignSrc' loaded lib = modifyMVar_ loaded \l -> do
when l $ unloadForeignLib lib
pure False
unloadForeignLib :: Ptr () -> IO ()
unloadForeignLib = dlclose . DLHandle
withForeignSrc :: ForeignSrc -> (Ptr () -> IO a) -> IO a
withForeignSrc ForeignSrc {..} f = withMVar foreignSrcLoaded
\case
True -> withForeignPtr foreignSrcFPtr f
False ->
panic "[FFI] withForeignSrc" ["Use of foreign library after unload"]
-- | An implementation of a foreign function.
data ForeignImpl = ForeignImpl
{ foreignImplFun :: FunPtr ()
-- | We don't need this to call the function but we want to keep the library
-- around as long as we still have a function from it so that it isn't
-- unloaded too early.
, foreignImplSrc :: ForeignSrc
}
-- | Load a 'ForeignImpl' with the given name from the given 'ForeignSrc'.
loadForeignImpl :: ForeignSrc -> String -> IO (Either FFILoadError ForeignImpl)
loadForeignImpl foreignImplSrc name =
withForeignSrc foreignImplSrc \lib ->
tryLoad (CantLoadFFIImpl name) do
foreignImplFun <- loadForeignFunPtr lib name
pure ForeignImpl {..}
loadForeignFunPtr :: Ptr () -> String -> IO (FunPtr ())
loadForeignFunPtr = dlsym . DLHandle
tryLoad :: (String -> FFILoadError) -> IO a -> IO (Either FFILoadError a)
tryLoad err = fmap (first $ err . displayException) . tryIOError
-- | Types which can be converted into libffi arguments.
--
-- The Storable constraint is so that we can put them in arrays.
class Storable a => FFIArg a where
ffiArg :: a -> Arg
instance FFIArg Word8 where
ffiArg = argWord8
instance FFIArg Word16 where
ffiArg = argWord16
instance FFIArg Word32 where
ffiArg = argWord32
instance FFIArg Word64 where
ffiArg = argWord64
instance FFIArg CFloat where
ffiArg = argCFloat
instance FFIArg CDouble where
ffiArg = argCDouble
instance FFIArg (Ptr a) where
ffiArg = argPtr
instance FFIArg CSize where
ffiArg = argCSize
-- | Types which can be returned from libffi.
--
-- The Storable constraint is so that we can put them in arrays.
class Storable a => FFIRet a where
ffiRet :: RetType a
instance FFIRet Word8 where
ffiRet = retWord8
instance FFIRet Word16 where
ffiRet = retWord16
instance FFIRet Word32 where
ffiRet = retWord32
instance FFIRet Word64 where
ffiRet = retWord64
instance FFIRet CFloat where
ffiRet = retCFloat
instance FFIRet CDouble where
ffiRet = retCDouble
instance FFIRet () where
ffiRet = retVoid
-- | Existential wrapper around a 'FFIArg'.
data SomeFFIArg = forall a. FFIArg a => SomeFFIArg a
-- | Call a 'ForeignImpl' with the given arguments. The type parameter decides
-- how the return value should be converted into a Haskell value.
callForeignImpl :: forall a. FFIRet a => ForeignImpl -> [SomeFFIArg] -> IO a
callForeignImpl ForeignImpl {..} xs = withForeignSrc foreignImplSrc \_ ->
callFFI foreignImplFun (ffiRet @a) $ map toArg xs
where toArg (SomeFFIArg x) = ffiArg x
#else
data ForeignSrc = ForeignSrc deriving (Show, Generic, NFData)
loadForeignSrc :: FilePath -> IO (Either FFILoadError ForeignSrc)
loadForeignSrc _ = pure $ Right ForeignSrc
unloadForeignSrc :: ForeignSrc -> IO ()
unloadForeignSrc _ = pure ()
#endif

View File

@ -0,0 +1,31 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
-- | Errors from dynamic loading of shared libraries for FFI.
module Cryptol.Backend.FFI.Error where
import Control.DeepSeq
import GHC.Generics
import Cryptol.Utils.PP
data FFILoadError
= CantLoadFFISrc
FilePath -- ^ Path to cryptol module
String -- ^ Error message
| CantLoadFFIImpl
String -- ^ Function name
String -- ^ Error message
deriving (Show, Generic, NFData)
instance PP FFILoadError where
ppPrec _ e =
case e of
CantLoadFFISrc path msg ->
hang (text "Could not load foreign source for module located at"
<+> text path <.> colon)
4 (text msg)
CantLoadFFIImpl name msg ->
hang (text "Could not load foreign implementation for binding"
<+> text name <.> colon)
4 (text msg)

View File

@ -54,7 +54,7 @@ import qualified Control.Exception as X
import Cryptol.Parser.Position
import Cryptol.Utils.Panic
import Cryptol.Utils.PP
import Cryptol.TypeCheck.AST(Name)
import Cryptol.TypeCheck.AST(Name, TParam)
-- | A computation that returns an already-evaluated value.
ready :: a -> Eval a
@ -412,17 +412,20 @@ evalPanic cxt = panic ("[Eval] " ++ cxt)
-- | Data type describing errors that can occur during evaluation.
data EvalError
= InvalidIndex (Maybe Integer) -- ^ Out-of-bounds index
| DivideByZero -- ^ Division or modulus by 0
| NegativeExponent -- ^ Exponentiation by negative integer
| LogNegative -- ^ Logarithm of a negative integer
| UserError String -- ^ Call to the Cryptol @error@ primitive
| LoopError String -- ^ Detectable nontermination
| NoPrim Name -- ^ Primitive with no implementation
| BadRoundingMode Integer -- ^ Invalid rounding mode
| BadValue String -- ^ Value outside the domain of a partial function.
= InvalidIndex (Maybe Integer) -- ^ Out-of-bounds index
| DivideByZero -- ^ Division or modulus by 0
| NegativeExponent -- ^ Exponentiation by negative integer
| LogNegative -- ^ Logarithm of a negative integer
| UserError String -- ^ Call to the Cryptol @error@ primitive
| LoopError String -- ^ Detectable nontermination
| NoPrim Name -- ^ Primitive with no implementation
| BadRoundingMode Integer -- ^ Invalid rounding mode
| BadValue String -- ^ Value outside the domain of a partial function.
| NoMatchingPropGuardCase String -- ^ No prop guard holds for the given type variables.
deriving Typeable
| FFINotSupported Name -- ^ Foreign function cannot be called
| FFITypeNumTooBig Name TParam Integer -- ^ Number passed to foreign function
-- as a type argument is too large
deriving Typeable
instance PP EvalError where
ppPrec _ e = case e of
@ -442,6 +445,17 @@ instance PP EvalError where
BadValue x -> "invalid input for" <+> backticks (text x)
NoPrim x -> text "unimplemented primitive:" <+> pp x
NoMatchingPropGuardCase msg -> text $ "No matching prop guard case; " ++ msg
FFINotSupported x -> vcat
[ text "cannot call foreign function" <+> pp x
, text "FFI calls are not supported in this context"
, text "If you are trying to evaluate an expression, try rebuilding"
, text " Cryptol with FFI support enabled."
]
FFITypeNumTooBig f p n -> vcat
[ text "numeric type argument to foreign function is too large:"
<+> integer n
, text "in type parameter" <+> pp p <+> "of function" <+> pp f
, text "type arguments must fit in a C `size_t`" ]
instance Show EvalError where
show = show . pp

View File

@ -438,9 +438,11 @@ declHole ::
sym -> Decl -> SEval sym (Name, Schema, SEval sym (GenValue sym), SEval sym (GenValue sym) -> SEval sym ())
declHole sym d =
case dDefinition d of
DPrim -> evalPanic "Unexpected primitive declaration in recursive group"
[show (ppLocName nm)]
DExpr _ -> do
DPrim -> evalPanic "Unexpected primitive declaration in recursive group"
[show (ppLocName nm)]
DForeign _ -> evalPanic "Unexpected foreign declaration in recursive group"
[show (ppLocName nm)]
DExpr _ -> do
(hole, fill) <- sDeclareHole sym msg
return (nm, sch, hole, fill)
where
@ -474,6 +476,18 @@ evalDecl sym renv env d = do
Just (Left ex) -> bindVar sym (dName d) (evalExpr sym renv ex) env
Nothing -> bindVar sym (dName d) (cryNoPrimError sym (dName d)) env
DForeign _ -> do
-- Foreign declarations should have been handled by the previous
-- Cryptol.Eval.FFI.evalForeignDecls pass already, so they should already
-- be in the environment. If not, then either Cryptol was not compiled
-- with FFI support enabled, or we are in a non-Concrete backend. In that
-- case, we just bind the name to an error computation which will raise an
-- error if we try to evaluate it.
case lookupVar (dName d) env of
Just _ -> pure env
Nothing -> bindVar sym (dName d)
(raiseError sym $ FFINotSupported $ dName d) env
DExpr e -> bindVar sym (dName d) (evalExpr sym renv e) env
@ -747,5 +761,6 @@ evalMatch sym (lsz, lenv) m = seq lsz $ case m of
where
f env =
case dDefinition d of
DPrim -> evalPanic "evalMatch" ["Unexpected local primitive"]
DExpr e -> evalExpr sym env e
DPrim -> evalPanic "evalMatch" ["Unexpected local primitive"]
DForeign _ -> evalPanic "evalMatch" ["Unexpected local foreign"]
DExpr e -> evalExpr sym env e

281
src/Cryptol/Eval/FFI.hs Normal file
View File

@ -0,0 +1,281 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
-- | Evaluation of foreign functions.
module Cryptol.Eval.FFI
( evalForeignDecls
) where
import Cryptol.Backend.FFI
import Cryptol.Backend.FFI.Error
import Cryptol.Eval
import Cryptol.ModuleSystem.Env
import Cryptol.TypeCheck.AST
#ifdef FFI_ENABLED
import Data.Either
import Data.IORef
import Data.Maybe
import Data.Proxy
import Data.Traversable
import Data.Word
import Foreign
import Foreign.C.Types
import GHC.Float
import LibBF (bfFromDouble, bfToDouble,
pattern NearEven)
import System.Directory
import Cryptol.Backend.Concrete
import Cryptol.Backend.FloatHelpers
import Cryptol.Backend.Monad
import Cryptol.Backend.SeqMap
import Cryptol.Eval.Env
import Cryptol.Eval.Prims
import Cryptol.Eval.Type
import Cryptol.Eval.Value
import Cryptol.ModuleSystem.Name
import Cryptol.TypeCheck.FFI.FFIType
import Cryptol.Utils.Ident
import Cryptol.Utils.RecordMap
-- | Find all the foreign declarations in the module and add them to the
-- environment. This is a separate pass from the main evaluation functions in
-- "Cryptol.Eval" since it only works for the Concrete backend.
--
-- Note: 'Right' is only returned if we successfully loaded some foreign
-- functions and the environment was modified. If there were no foreign
-- declarations at all then @Left []@ is returned, so 'Left' does not
-- necessarily indicate an error.
evalForeignDecls :: ModulePath -> Module -> EvalEnv ->
Eval (Either [FFILoadError] (ForeignSrc, EvalEnv))
evalForeignDecls path m env = io
case mapMaybe getForeign $ mDecls m of
[] -> pure $ Left []
foreigns ->
case path of
InFile p -> canonicalizePath p >>= loadForeignSrc >>=
\case
Right fsrc -> collect <$> for foreigns \(name, ffiType) ->
fmap ((name,) . foreignPrimPoly name ffiType) <$>
loadForeignImpl fsrc (unpackIdent $ nameIdent name)
where collect (partitionEithers -> (errs, primMap))
| null errs = Right
(fsrc, foldr (uncurry bindVarDirect) env primMap)
| otherwise = Left errs
Left err -> pure $ Left [err]
-- We don't handle in-memory modules for now
InMem _ _ -> evalPanic "evalForeignDecls"
["Can't find foreign source of in-memory module"]
where getForeign (NonRecursive Decl { dName, dDefinition = DForeign ffiType })
= Just (dName, ffiType)
getForeign _ = Nothing
-- | Generate a 'Prim' value representing the given foreign function, containing
-- all the code necessary to marshal arguments and return values and do the
-- actual FFI call.
foreignPrimPoly :: Name -> FFIFunType -> ForeignImpl -> Prim Concrete
foreignPrimPoly name fft impl = buildNumPoly (ffiTParams fft) mempty
where -- Add type lambdas for the type parameters and build a type environment
-- that we can look up later to compute e.g. array sizes.
--
-- Given [p1, p2, ..., pk] {}, returns
-- PNumPoly \n1 -> PNumPoly \n2 -> ... PNumPoly \nk ->
-- foreignPrim name fft impl {p1 = n1, p2 = n2, ..., pk = nk}
buildNumPoly (tp:tps) tenv = PNumPoly \n ->
buildNumPoly tps $ bindTypeVar (TVBound tp) (Left n) tenv
buildNumPoly [] tenv = foreignPrim name fft impl tenv
-- | Methods for obtaining a return value. The producer of this type must supply
-- both 1) a polymorphic IO object directly containing a return value that the
-- consumer can instantiate at any 'FFIRet' type, and 2) an effectful function
-- that takes some output arguments and modifies what they are pointing at to
-- store a return value. The consumer can choose which one to use.
data GetRet = GetRet
{ getRetAsValue :: forall a. FFIRet a => IO a
, getRetAsOutArgs :: [SomeFFIArg] -> IO () }
-- | Generate the monomorphic part of the foreign 'Prim', given a 'TypeEnv'
-- containing all the type arguments we have already received.
foreignPrim :: Name -> FFIFunType -> ForeignImpl -> TypeEnv -> Prim Concrete
foreignPrim name FFIFunType {..} impl tenv = buildFun ffiArgTypes []
where
-- Build up the 'Prim' function for the FFI call.
--
-- Given [t1, t2 ... tm] we return
-- PStrict \v1 -> PStrict \v2 -> ... PStrict \vm -> PPrim $
-- marshalArg t1 v1 \a1 ->
-- marshalArg t2 v2 \a2 -> ... marshalArg tm vm \am ->
-- marshalRet ffiRetType GetRet
-- { getRetAsValue = callForeignImpl impl [n1, ..., nk, a1, ..., am]
-- , getRetAsOutArgs = \[o1, ..., ol] ->
-- callForeignImpl impl [n1, ..., nk, a1, ..., am, o1, ..., ol] }
buildFun :: [FFIType] -> [(FFIType, GenValue Concrete)] -> Prim Concrete
buildFun (argType:argTypes) typesAndVals = PStrict \val ->
buildFun argTypes $ typesAndVals ++ [(argType, val)]
buildFun [] typesAndVals = PPrim $
marshalArgs typesAndVals \inArgs -> do
tyArgs <- traverse marshalTyArg ffiTParams
let tyInArgs = tyArgs ++ inArgs
marshalRet ffiRetType GetRet
{ getRetAsValue = callForeignImpl impl tyInArgs
, getRetAsOutArgs = callForeignImpl impl . (tyInArgs ++) }
-- Look up the value of a type parameter in the type environment and marshal
-- it.
marshalTyArg :: TParam -> Eval SomeFFIArg
marshalTyArg tp
| n <= toInteger (maxBound :: CSize) =
pure $ SomeFFIArg @CSize $ fromInteger n
| otherwise = raiseError Concrete $ FFITypeNumTooBig name tp n
where n = evalFinType $ TVar $ TVBound tp
-- Marshal the given value as the given FFIType and call the given function
-- with the results. A single Cryptol argument may correspond to any number of
-- C arguments, so the callback takes a list.
--
-- NOTE: the result must be used only in the callback since it may have a
-- limited lifetime (e.g. pointer returned by alloca).
marshalArg :: FFIType -> GenValue Concrete ->
([SomeFFIArg] -> Eval a) -> Eval a
marshalArg FFIBool val f = f [SomeFFIArg @Word8 $ fromBool $ fromVBit val]
marshalArg (FFIBasic t) val f = getMarshalBasicArg t \m -> do
arg <- m val
f [SomeFFIArg arg]
marshalArg (FFIArray (evalFinType -> n) t) val f =
getMarshalBasicArg t \m -> do
args <- traverse (>>= m) $ enumerateSeqMap n $ fromVSeq val
-- Since we need to do an Eval action in an IO callback, we need to
-- manually unwrap and wrap the Eval datatype
Eval \stk ->
withArray args \ptr ->
runEval stk $ f [SomeFFIArg ptr]
marshalArg (FFITuple types) val f = do
vals <- sequence $ fromVTuple val
marshalArgs (zip types vals) f
marshalArg (FFIRecord typeMap) val f = do
vals <- traverse (`lookupRecord` val) $ displayOrder typeMap
marshalArgs (zip (displayElements typeMap) vals) f
-- Call marshalArg on a bunch of arguments and collect the results together
-- (in the order of the arguments).
marshalArgs :: [(FFIType, GenValue Concrete)] ->
([SomeFFIArg] -> Eval a) -> Eval a
marshalArgs typesAndVals f = go typesAndVals []
where go [] args = f args
go ((t, v):tvs) prevArgs = marshalArg t v \currArgs ->
go tvs (prevArgs ++ currArgs)
-- Given a FFIType and a GetRet, obtain a return value and convert it to a
-- Cryptol value. The return value is obtained differently depending on the
-- FFIType.
marshalRet :: FFIType -> GetRet -> Eval (GenValue Concrete)
marshalRet FFIBool gr = VBit . toBool <$> io (getRetAsValue gr @Word8)
marshalRet (FFIBasic t) gr = getMarshalBasicRet t (io (getRetAsValue gr) >>=)
marshalRet (FFIArray (evalFinType -> n) t) gr = getMarshalBasicRet t \m ->
fmap (VSeq n . finiteSeqMap Concrete . map m) $
io $ allocaArray (fromInteger n) \ptr -> do
getRetAsOutArgs gr [SomeFFIArg ptr]
peekArray (fromInteger n) ptr
marshalRet (FFITuple types) gr = VTuple <$> marshalMultiRet types gr
marshalRet (FFIRecord typeMap) gr =
VRecord . recordFromFields . zip (displayOrder typeMap) <$>
marshalMultiRet (displayElements typeMap) gr
-- Obtain multiple return values as output arguments for a composite return
-- type. Each return value is fully evaluated but put back in an Eval since
-- VTuple and VRecord expect it.
marshalMultiRet :: [FFIType] -> GetRet -> Eval [Eval (GenValue Concrete)]
-- Since IO callbacks are involved we just do the whole thing in IO and wrap
-- it in an Eval at the end. This should be fine since we are not changing
-- the (Cryptol) call stack.
marshalMultiRet types gr = Eval \stk -> do
-- We use this IORef hack here since we are calling marshalRet recursively
-- but marshalRet doesn't let us return any extra information from the
-- callback through to the result of the function. So we remember the result
-- as a side effect.
vals <- newIORef []
let go [] args = getRetAsOutArgs gr args
go (t:ts) prevArgs = do
val <- runEval stk $ marshalRet t $ getRetFromAsOutArgs \currArgs ->
go ts (prevArgs ++ currArgs)
modifyIORef' vals (val :)
go types []
map pure <$> readIORef vals
-- Evaluate a finite numeric type expression.
evalFinType :: Type -> Integer
evalFinType = finNat' . evalNumType tenv
-- | Given a way to 'getRetAsOutArgs', create a 'GetRet', where the
-- 'getRetAsValue' simply allocates a temporary space to call 'getRetAsOutArgs'
-- on. This is useful for return types that we know how to obtain directly as a
-- value but need to obtain as an output argument when multiple return values
-- are involved.
getRetFromAsOutArgs :: ([SomeFFIArg] -> IO ()) -> GetRet
getRetFromAsOutArgs f = GetRet
{ getRetAsValue = alloca \ptr -> do
f [SomeFFIArg ptr]
peek ptr
, getRetAsOutArgs = f }
-- | Given a 'FFIBasicType', call the callback with a marshalling function that
-- marshals values to the 'FFIArg' type corresponding to the 'FFIBasicType'.
-- The callback must be able to handle marshalling functions that marshal to any
-- 'FFIArg' type.
getMarshalBasicArg :: FFIBasicType ->
(forall a. FFIArg a => (GenValue Concrete -> Eval a) -> b) -> b
getMarshalBasicArg (FFIWord _ s) f = withWordType s \(_ :: p t) ->
f @t $ fmap (fromInteger . bvVal) . fromVWord Concrete "getMarshalBasicArg"
getMarshalBasicArg (FFIFloat _ _ s) f =
case s of
-- LibBF can only convert to 'Double' directly, so we do that first then
-- convert to 'Float', which should not result in any loss of precision if
-- the original data was 32-bit anyways.
FFIFloat32 -> f $ pure . CFloat . double2Float . toDouble
FFIFloat64 -> f $ pure . CDouble . toDouble
where toDouble = fst . bfToDouble NearEven . bfValue . fromVFloat
-- | Given a 'FFIBasicType', call the callback with an unmarshalling function
-- from the 'FFIRet' type corresponding to the 'FFIBasicType' to Cryptol values.
-- The callback must be able to handle unmarshalling functions from any 'FFIRet'
-- type.
getMarshalBasicRet :: FFIBasicType ->
(forall a. FFIRet a => (a -> Eval (GenValue Concrete)) -> b) -> b
getMarshalBasicRet (FFIWord n s) f = withWordType s \(_ :: p t) ->
f @t $ word Concrete n . toInteger
getMarshalBasicRet (FFIFloat e p s) f =
case s of
FFIFloat32 -> f $ toValue . \case CFloat x -> float2Double x
FFIFloat64 -> f $ toValue . \case CDouble x -> x
where toValue = pure . VFloat . BF e p . bfFromDouble
-- | Call the callback with the Word type corresponding to the given
-- 'FFIWordSize'.
withWordType :: FFIWordSize ->
(forall a. (FFIArg a, FFIRet a, Integral a) => Proxy a -> b) -> b
withWordType FFIWord8 f = f $ Proxy @Word8
withWordType FFIWord16 f = f $ Proxy @Word16
withWordType FFIWord32 f = f $ Proxy @Word32
withWordType FFIWord64 f = f $ Proxy @Word64
#else
-- | Dummy implementation for when FFI is disabled. Does not add anything to
-- the environment.
evalForeignDecls :: ModulePath -> Module -> EvalEnv ->
Eval (Either [FFILoadError] (ForeignSrc, EvalEnv))
evalForeignDecls _ _ _ = pure $ Left []
#endif

View File

@ -511,8 +511,9 @@ the new bindings.
> evalDecl :: Env -> Decl -> (Name, E Value)
> evalDecl env d =
> case dDefinition d of
> DPrim -> (dName d, pure (evalPrim (dName d)))
> DExpr e -> (dName d, evalExpr env e)
> DPrim -> (dName d, pure (evalPrim (dName d)))
> DForeign _ -> (dName d, cryError $ FFINotSupported $ dName d)
> DExpr e -> (dName d, evalExpr env e)
>
Newtypes

View File

@ -93,6 +93,7 @@ instance FreeVars Decl where
instance FreeVars DeclDef where
freeVars d = case d of
DPrim -> mempty
DForeign _ -> mempty
DExpr e -> freeVars e

View File

@ -64,8 +64,9 @@ findModule n env = runModuleM env (Base.findModule n)
-- | Load the module contained in the given file.
loadModuleByPath :: FilePath -> ModuleCmd (ModulePath,T.Module)
loadModuleByPath path minp =
runModuleM minp{ minpModuleEnv = resetModuleEnv (minpModuleEnv minp) } $ do
loadModuleByPath path minp = do
moduleEnv' <- resetModuleEnv $ minpModuleEnv minp
runModuleM minp{ minpModuleEnv = moduleEnv' } $ do
unloadModule ((InFile path ==) . lmFilePath)
m <- Base.loadModuleByPath path
setFocusedModule (T.mName m)
@ -73,8 +74,9 @@ loadModuleByPath path minp =
-- | Load the given parsed module.
loadModuleByName :: P.ModName -> ModuleCmd (ModulePath,T.Module)
loadModuleByName n minp =
runModuleM minp{ minpModuleEnv = resetModuleEnv (minpModuleEnv minp) } $ do
loadModuleByName n minp = do
moduleEnv' <- resetModuleEnv $ minpModuleEnv minp
runModuleM minp{ minpModuleEnv = moduleEnv' } $ do
unloadModule ((n ==) . lmName)
(path,m') <- Base.loadModuleFrom False (FromModule n)
setFocusedModule (T.mName m')

View File

@ -11,6 +11,7 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
@ -18,6 +19,7 @@ module Cryptol.ModuleSystem.Base where
import qualified Control.Exception as X
import Control.Monad (unless,when)
import Data.Functor.Compose
import Data.Maybe (fromMaybe)
import Data.Monoid ((<>))
import Data.Text.Encoding (decodeUtf8')
@ -52,6 +54,7 @@ import Cryptol.ModuleSystem.Env (lookupModule
import qualified Cryptol.Eval as E
import qualified Cryptol.Eval.Concrete as Concrete
import Cryptol.Eval.Concrete (Concrete(..))
import Cryptol.Eval.FFI
import qualified Cryptol.ModuleSystem.NamingEnv as R
import qualified Cryptol.ModuleSystem.Renamer as R
import qualified Cryptol.Parser as P
@ -248,8 +251,17 @@ doLoadModule quiet isrc path fp pm0 =
let ?evalPrim = \i -> Right <$> Map.lookup i tbl
callStacks <- getCallStacks
let ?callStacks = callStacks
unless (T.isParametrizedModule tcm) $ modifyEvalEnv (E.moduleEnv Concrete tcm)
loadedModule path fp nameEnv tcm
foreignSrc <-
if T.isParametrizedModule tcm
then pure Nothing
else (getCompose
<$> modifyEvalEnvM (fmap Compose . evalForeignDecls path tcm)
>>= \case
Left [] -> pure Nothing
Left errs -> ffiLoadErrors (T.mName tcm) errs
Right (foreignSrc, ()) -> pure (Just foreignSrc))
<* modifyEvalEnv (E.moduleEnv Concrete tcm)
loadedModule path fp nameEnv foreignSrc tcm
return tcm
where

View File

@ -18,6 +18,7 @@ module Cryptol.ModuleSystem.Env where
import Paths_cryptol (getDataDir)
#endif
import Cryptol.Backend.FFI (ForeignSrc, unloadForeignSrc)
import Cryptol.Eval (EvalEnv)
import Cryptol.ModuleSystem.Fingerprint
import Cryptol.ModuleSystem.Interface
@ -41,6 +42,7 @@ import System.Directory (getAppUserDataDirectory, getCurrentDirectory)
import System.Environment(getExecutablePath)
import System.FilePath ((</>), normalise, joinPath, splitPath, takeDirectory)
import qualified Data.List as List
import Data.Foldable
import GHC.Generics (Generic)
import Control.DeepSeq
@ -99,14 +101,19 @@ data CoreLint = NoCoreLint -- ^ Don't run core lint
| CoreLint -- ^ Run core lint
deriving (Generic, NFData)
resetModuleEnv :: ModuleEnv -> ModuleEnv
resetModuleEnv env = env
{ meLoadedModules = mempty
, meNameSeeds = T.nameSeeds
, meEvalEnv = mempty
, meFocusedModule = Nothing
, meDynEnv = mempty
}
resetModuleEnv :: ModuleEnv -> IO ModuleEnv
resetModuleEnv env = do
for_ (getLoadedModules $ meLoadedModules env) $ \lm ->
case lmForeignSrc lm of
Just fsrc -> unloadForeignSrc fsrc
_ -> pure ()
pure env
{ meLoadedModules = mempty
, meNameSeeds = T.nameSeeds
, meEvalEnv = mempty
, meFocusedModule = Nothing
, meDynEnv = mempty
}
initialModuleEnv :: IO ModuleEnv
initialModuleEnv = do
@ -342,6 +349,9 @@ data LoadedModule = LoadedModule
-- ^ The actual type-checked module
, lmFingerprint :: Fingerprint
, lmForeignSrc :: Maybe ForeignSrc
-- ^ The dynamically loaded source for any foreign functions in the module
} deriving (Show, Generic, NFData)
-- | Has this module been loaded already.
@ -362,9 +372,9 @@ lookupModule mn me = search lmLoadedModules `mplus` search lmLoadedParamModules
-- | Add a freshly loaded module. If it was previously loaded, then
-- the new version is ignored.
addLoadedModule ::
ModulePath -> String -> Fingerprint -> R.NamingEnv -> T.Module ->
LoadedModules -> LoadedModules
addLoadedModule path ident fp nameEnv tm lm
ModulePath -> String -> Fingerprint -> R.NamingEnv -> Maybe ForeignSrc ->
T.Module -> LoadedModules -> LoadedModules
addLoadedModule path ident fp nameEnv fsrc tm lm
| isLoaded (T.mName tm) lm = lm
| T.isParametrizedModule tm = lm { lmLoadedParamModules = loaded :
lmLoadedParamModules lm }
@ -379,6 +389,7 @@ addLoadedModule path ident fp nameEnv tm lm
, lmInterface = T.genIface tm
, lmModule = tm
, lmFingerprint = fp
, lmForeignSrc = fsrc
}
-- | Remove a previously loaded module.

View File

@ -231,8 +231,9 @@ instance Inst DeclGroup where
instance Inst DeclDef where
inst env d =
case d of
DPrim -> DPrim
DExpr e -> DExpr (inst env e)
DPrim -> DPrim
DForeign t -> DForeign t
DExpr e -> DExpr (inst env e)
instance Inst Decl where
inst env d = d { dSignature = inst env (dSignature d)

View File

@ -14,6 +14,8 @@ module Cryptol.ModuleSystem.Monad where
import Cryptol.Eval (EvalEnv,EvalOpts(..))
import Cryptol.Backend.FFI (ForeignSrc)
import Cryptol.Backend.FFI.Error
import qualified Cryptol.Backend.Monad as E
import Cryptol.ModuleSystem.Env
@ -43,9 +45,11 @@ import Control.Monad.IO.Class
import Control.Exception (IOException)
import Data.ByteString (ByteString)
import Data.Function (on)
import Data.Functor.Identity
import Data.Map (Map)
import Data.Maybe (isJust)
import Data.Text.Encoding.Error (UnicodeException)
import Data.Traversable
import MonadLib
import System.Directory (canonicalizePath)
@ -116,6 +120,8 @@ data ModuleError
| FailedToParameterizeModDefs P.ModName [T.Name]
-- ^ Failed to add the module parameters to all definitions in a module.
| NotAParameterizedModule P.ModName
| FFILoadErrors P.ModName [FFILoadError]
-- ^ Errors loading foreign function implementations
| ErrorInFile ModulePath ModuleError
-- ^ This is just a tag on the error, indicating the file containing it.
@ -145,6 +151,7 @@ instance NFData ModuleError where
ImportedParamModule x -> x `deepseq` ()
FailedToParameterizeModDefs x xs -> x `deepseq` xs `deepseq` ()
NotAParameterizedModule x -> x `deepseq` ()
FFILoadErrors x errs -> x `deepseq` errs `deepseq` ()
ErrorInFile x y -> x `deepseq` y `deepseq` ()
instance PP ModuleError where
@ -213,6 +220,11 @@ instance PP ModuleError where
NotAParameterizedModule x ->
text "[error] Module" <+> pp x <+> text "does not have parameters."
FFILoadErrors x errs ->
hang (text "[error] Failed to load foreign implementations for module"
<+> pp x <.> colon)
4 (vcat $ map pp errs)
ErrorInFile _ x -> ppPrec prec x
moduleNotFound :: P.ModName -> [FilePath] -> ModuleM a
@ -277,6 +289,9 @@ failedToParameterizeModDefs x xs =
notAParameterizedModule :: P.ModName -> ModuleM a
notAParameterizedModule x = ModuleT (raise (NotAParameterizedModule x))
ffiLoadErrors :: P.ModName -> [FFILoadError] -> ModuleM a
ffiLoadErrors x errs = ModuleT (raise (FFILoadErrors x errs))
-- | Run the computation, and if it caused and error, tag the error
-- with the given file.
errorInFile :: ModulePath -> ModuleM a -> ModuleM a
@ -522,22 +537,27 @@ unloadModule rm = ModuleT $ do
set $! env { meLoadedModules = removeLoadedModule rm (meLoadedModules env) }
loadedModule ::
ModulePath -> Fingerprint -> NamingEnv -> T.Module -> ModuleM ()
loadedModule path fp nameEnv m = ModuleT $ do
ModulePath -> Fingerprint -> NamingEnv -> Maybe ForeignSrc -> T.Module ->
ModuleM ()
loadedModule path fp nameEnv fsrc m = ModuleT $ do
env <- get
ident <- case path of
InFile p -> unModuleT $ io (canonicalizePath p)
InMem l _ -> pure l
set $! env { meLoadedModules = addLoadedModule path ident fp nameEnv m
set $! env { meLoadedModules = addLoadedModule path ident fp nameEnv fsrc m
(meLoadedModules env) }
modifyEvalEnv :: (EvalEnv -> E.Eval EvalEnv) -> ModuleM ()
modifyEvalEnv f = ModuleT $ do
modifyEvalEnvM :: Traversable t =>
(EvalEnv -> E.Eval (t EvalEnv)) -> ModuleM (t ())
modifyEvalEnvM f = ModuleT $ do
env <- get
let evalEnv = meEvalEnv env
evalEnv' <- inBase $ E.runEval mempty (f evalEnv)
set $! env { meEvalEnv = evalEnv' }
inBase (E.runEval mempty (f evalEnv))
>>= traverse (\evalEnv' -> set $! env { meEvalEnv = evalEnv' })
modifyEvalEnv :: (EvalEnv -> E.Eval EvalEnv) -> ModuleM ()
modifyEvalEnv = fmap runIdentity . modifyEvalEnvM . (fmap Identity .)
getEvalEnv :: ModuleM EvalEnv
getEvalEnv = ModuleT (meEvalEnv `fmap` get)

View File

@ -683,6 +683,7 @@ instance Rename Bind where
instance Rename BindDef where
rename DPrim = return DPrim
rename DForeign = return DForeign
rename (DExpr e) = DExpr <$> rename e
rename (DPropGuards cases) = DPropGuards <$> traverse renameCase cases
where

View File

@ -90,6 +90,7 @@ import Paths_cryptol
'primitive' { Located $$ (Token (KW KW_primitive) _)}
'constraint'{ Located $$ (Token (KW KW_constraint) _)}
'foreign' { Located $$ (Token (KW KW_foreign) _)}
'Prop' { Located $$ (Token (KW KW_Prop) _)}
'[' { Located $$ (Token (Sym BracketL) _)}
@ -251,6 +252,7 @@ vtop_decl :: { [TopDecl PName] }
{ [exportDecl $1 Public (mkProperty $3 [] $5)] }
| mbDoc newtype { [exportNewtype Public $1 $2] }
| prim_bind { $1 }
| foreign_bind { $1 }
| private_decls { $1 }
| parameter_decls { $1 }
| mbDoc 'submodule'
@ -273,6 +275,8 @@ prim_bind :: { [TopDecl PName] }
| mbDoc 'primitive' '(' op ')' ':' schema { mkPrimDecl $1 $4 $7 }
| mbDoc 'primitive' 'type' schema ':' kind {% mkPrimTypeDecl $1 $4 $6 }
foreign_bind :: { [TopDecl PName] }
: mbDoc 'foreign' name ':' schema { mkForeignDecl $1 $3 $5 }
parameter_decls :: { [TopDecl PName] }
: 'parameter' 'v{' par_decls 'v}' { reverse $3 }

View File

@ -279,6 +279,7 @@ data Bind name = Bind
type LBindDef = Located (BindDef PName)
data BindDef name = DPrim
| DForeign
| DExpr (Expr name)
| DPropGuards [([Prop name], Expr name)]
deriving (Eq, Show, Generic, NFData, Functor)
@ -727,6 +728,7 @@ instance (Show name, PPName name) => PP (Bind name) where
instance (Show name, PPName name) => PP (BindDef name) where
ppPrec _ DPrim = text "<primitive>"
ppPrec _ DForeign = text "<foreign>"
ppPrec p (DExpr e) = ppPrec p e
ppPrec _p (DPropGuards _guards) = text "propguards"

View File

@ -126,6 +126,7 @@ $white+ { emit $ White Space }
"primitive" { emit $ KW KW_primitive }
"parameter" { emit $ KW KW_parameter }
"constraint" { emit $ KW KW_constraint }
"foreign" { emit $ KW KW_foreign }
"Prop" { emit $ KW KW_Prop }

View File

@ -65,6 +65,7 @@ namesB b =
namesDef :: Ord name => BindDef name -> Set name
namesDef DPrim = Set.empty
namesDef DForeign = Set.empty
namesDef (DExpr e) = namesE e
namesDef (DPropGuards guards) = mconcat . fmap (\(_props, e) -> namesE e) $ guards
@ -186,6 +187,7 @@ tnamesB b = Set.unions [setS, setP, setE]
tnamesDef :: Ord name => BindDef name -> Set name
tnamesDef DPrim = Set.empty
tnamesDef DForeign = Set.empty
tnamesDef (DExpr e) = tnamesE e
tnamesDef (DPropGuards guards) = mconcat . fmap (\(_props, e) -> tnamesE e) $ guards

View File

@ -219,6 +219,11 @@ noMatchB b =
| otherwise -> panic "NoPat" [ "noMatchB: primitive with params"
, show b ]
DForeign
| null (bParams b) -> return b
| otherwise -> panic "NoPat" [ "noMatchB: foreign with params"
, show b ]
DExpr e ->
do e' <- noPatFun (Just (thing (bName b))) 0 (bParams b) e
return b { bParams = [], bDef = DExpr e' <$ bDef b }

View File

@ -645,19 +645,26 @@ mkIf ifThens theElse = foldr addIfThen theElse ifThens
where
addIfThen (cond, doexpr) elseExpr = EIf cond doexpr elseExpr
-- | Generate a signature and a primitive binding. The reason for generating
-- both instead of just adding the signature at this point is that it means the
-- primitive declarations don't need to be treated differently in the noPat
mkPrimDecl :: Maybe (Located Text) -> LPName -> Schema PName -> [TopDecl PName]
mkPrimDecl = mkNoImplDecl DPrim
mkForeignDecl :: Maybe (Located Text) -> LPName -> Schema PName -> [TopDecl PName]
mkForeignDecl = mkNoImplDecl DForeign
-- | Generate a signature and a binding for value declarations with no
-- implementation (i.e. primitive or foreign declarations). The reason for
-- generating both instead of just adding the signature at this point is that it
-- means the declarations don't need to be treated differently in the noPat
-- pass. This is also the reason we add the doc to the TopLevel constructor,
-- instead of just place it on the binding directly. A better solution might be
-- to just have a different constructor for primitives.
mkPrimDecl ::
Maybe (Located Text) -> LPName -> Schema PName -> [TopDecl PName]
mkPrimDecl mbDoc ln sig =
-- to just have a different constructor for primitives and foreigns.
mkNoImplDecl :: BindDef PName
-> Maybe (Located Text) -> LPName -> Schema PName -> [TopDecl PName]
mkNoImplDecl def mbDoc ln sig =
[ exportDecl mbDoc Public
$ DBind Bind { bName = ln
, bParams = []
, bDef = at sig (Located emptyRange DPrim)
, bDef = at sig (Located emptyRange def)
, bSignature = Nothing
, bPragmas = []
, bMono = False

View File

@ -37,6 +37,11 @@ data Range = Range { from :: !Position
, source :: FilePath }
deriving (Eq, Ord, Show, Generic, NFData)
-- | Returns `True` if the first range is contained in the second one.
rangeWithin :: Range -> Range -> Bool
a `rangeWithin` b =
source a == source b && from a >= from b && to a <= to b
-- | An empty range.
--
-- Caution: using this on the LHS of a use of rComb will cause the empty source

View File

@ -51,6 +51,7 @@ data TokenKW = KW_else
| KW_primitive
| KW_parameter
| KW_constraint
| KW_foreign
| KW_Prop
| KW_by
| KW_down

View File

@ -147,11 +147,12 @@ instance AddParams DeclGroup where
instance AddParams Decl where
addParams ps d =
case dDefinition d of
DPrim -> d
DExpr e -> d { dSignature = addParams ps (dSignature d)
, dDefinition = DExpr (addParams ps e)
, dName = toParamInstName (dName d)
}
DPrim -> d
DForeign _ -> d
DExpr e -> d { dSignature = addParams ps (dSignature d)
, dDefinition = DExpr (addParams ps e)
, dName = toParamInstName (dName d)
}
instance AddParams TySyn where
addParams ps ts = ts { tsParams = pTypes ps ++ tsParams ts
@ -280,6 +281,7 @@ instance Inst DeclDef where
inst ps d =
case d of
DPrim -> DPrim
DForeign t -> DForeign t
DExpr e -> DExpr (inst ps e)
instance Inst Type where

View File

@ -218,8 +218,9 @@ rewD rews d = do e <- rewDef rews (dDefinition d)
return d { dDefinition = e }
rewDef :: RewMap -> DeclDef -> M DeclDef
rewDef rews (DExpr e) = DExpr <$> rewE rews e
rewDef _ DPrim = return DPrim
rewDef rews (DExpr e) = DExpr <$> rewE rews e
rewDef _ DPrim = return DPrim
rewDef _ (DForeign t) = return $ DForeign t
rewDeclGroup :: RewMap -> DeclGroup -> M DeclGroup
rewDeclGroup rews dg =
@ -239,11 +240,12 @@ rewDeclGroup rews dg =
consider d =
case dDefinition d of
DPrim -> Left d
DExpr e -> let (tps,props,e') = splitTParams e
in if not (null tps) && notFun e'
then Right (d, tps, props, e')
else Left d
DPrim -> Left d
DForeign _ -> Left d
DExpr e -> let (tps,props,e') = splitTParams e
in if not (null tps) && notFun e'
then Right (d, tps, props, e')
else Left d
rewSame ds =
do new <- forM (NE.toList ds) $ \(d,_,_,e) ->

View File

@ -188,9 +188,10 @@ specializeConst e0 = do
sig' <- instantiateSchema ts n (dSignature decl)
modifySpecCache (Map.adjust (fmap (insertTM ts (qname', Nothing))) qname)
rhs' <- case dDefinition decl of
DExpr e -> do e' <- specializeExpr =<< instantiateExpr ts n e
return (DExpr e')
DPrim -> return DPrim
DExpr e -> do e' <- specializeExpr =<< instantiateExpr ts n e
return (DExpr e')
DPrim -> return DPrim
DForeign t -> return $ DForeign t
let decl' = decl { dName = qname', dSignature = sig', dDefinition = rhs' }
modifySpecCache (Map.adjust (fmap (insertTM ts (qname', Just decl'))) qname)
return (EVar qname')

View File

@ -39,6 +39,7 @@ import Cryptol.Parser.AST ( Selector(..),Pragma(..)
, Fixity(..))
import Cryptol.Utils.Ident (Ident,isInfixIdent,ModName,PrimIdent,prelPrim)
import Cryptol.Utils.RecordMap
import Cryptol.TypeCheck.FFI.FFIType
import Cryptol.TypeCheck.PP
import Cryptol.TypeCheck.Type
@ -180,6 +181,7 @@ data Decl = Decl { dName :: !Name
} deriving (Generic, NFData, Show)
data DeclDef = DPrim
| DForeign FFIFunType
| DExpr Expr
deriving (Show, Generic, NFData)
@ -375,8 +377,9 @@ instance PP (WithNames Decl) where
++ [ nest 2 (sep [pp dName <+> text "=", ppWithNames nm dDefinition]) ]
instance PP (WithNames DeclDef) where
ppPrec _ (WithNames DPrim _) = text "<primitive>"
ppPrec _ (WithNames (DExpr e) nm) = ppWithNames nm e
ppPrec _ (WithNames DPrim _) = text "<primitive>"
ppPrec _ (WithNames (DForeign _) _) = text "<foreign>"
ppPrec _ (WithNames (DExpr e) nm) = ppWithNames nm e
instance PP Decl where
ppPrec = ppWithNamesPrec IntMap.empty

View File

@ -13,12 +13,13 @@ import Data.List((\\),sortBy,groupBy,partition)
import Data.Function(on)
import qualified Cryptol.Parser.AST as P
import Cryptol.Parser.Position(Located(..), Range(..))
import Cryptol.Parser.Position(Located(..), Range(..), rangeWithin)
import Cryptol.TypeCheck.PP
import Cryptol.TypeCheck.Type
import Cryptol.TypeCheck.InferTypes
import Cryptol.TypeCheck.Subst
import Cryptol.TypeCheck.Unify(Path,isRootPath)
import Cryptol.TypeCheck.FFI.Error
import Cryptol.ModuleSystem.Name(Name)
import Cryptol.Utils.Ident(Ident)
import Cryptol.Utils.RecordMap
@ -56,6 +57,8 @@ cleanupErrors = dropErrorsFromSameLoc
-- | Should the first error suppress the next one.
subsumes :: (Range,Error) -> (Range,Error) -> Bool
subsumes (_,NotForAll _ _ x _) (_,NotForAll _ _ y _) = x == y
subsumes (r1,UnexpectedTypeWildCard) (r2,UnsupportedFFIType{}) =
r1 `rangeWithin` r2
subsumes (r1,KindMismatch {}) (r2,err) =
case err of
KindMismatch {} -> r1 == r2
@ -144,6 +147,11 @@ data Error = KindMismatch (Maybe TypeSource) Kind Kind
| MissingModTParam (Located Ident)
| MissingModVParam (Located Ident)
| UnsupportedFFIKind TypeSource TParam Kind
-- ^ Kind is not supported for FFI
| UnsupportedFFIType TypeSource FFITypeError
-- ^ Type is not supported for FFI
| TemporaryError Doc
-- ^ This is for errors that don't fit other cateogories.
-- We should not use it much, and is generally to be used
@ -200,7 +208,9 @@ errorImportance err =
AmbiguousSize {} -> 2
UnsupportedFFIKind {} -> 10
UnsupportedFFIType {} -> 7
-- less than UnexpectedTypeWildCard
instance TVars Warning where
@ -252,6 +262,9 @@ instance TVars Error where
MissingModTParam {} -> err
MissingModVParam {} -> err
UnsupportedFFIKind {} -> err
UnsupportedFFIType src e -> UnsupportedFFIType src !$ apSubst su e
TemporaryError {} -> err
@ -287,6 +300,9 @@ instance FVS Error where
MissingModTParam {} -> Set.empty
MissingModVParam {} -> Set.empty
UnsupportedFFIKind {} -> Set.empty
UnsupportedFFIType _ t -> fvs t
TemporaryError {} -> Set.empty
instance PP Warning where
@ -329,7 +345,7 @@ instance PP (WithNames Error) where
UnexpectedTypeWildCard ->
addTVarsDescsAfter names err $
nested "Wild card types are not allowed in this context"
"(e.g., they cannot be used in type synonyms)."
"(e.g., they cannot be used in type synonyms or FFI declarations)."
KindMismatch mbsrc k1 k2 ->
addTVarsDescsAfter names err $
@ -464,6 +480,17 @@ instance PP (WithNames Error) where
MissingModVParam x ->
"Missing definition for value parameter" <+> quotes (pp (thing x))
UnsupportedFFIKind src param k ->
nested "Kind of type variable unsupported for FFI: " $
vcat
[ pp param <+> colon <+> pp k
, "Only type variables of kind" <+> pp KNum <+> "are supported"
, "When checking" <+> pp src ]
UnsupportedFFIType src t -> vcat
[ ppWithNames names t
, "When checking" <+> pp src ]
TemporaryError doc -> doc
where
bullets xs = vcat [ "" <+> d | d <- xs ]

View File

@ -0,0 +1,102 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
-- | Checking and conversion of 'Type's to 'FFIType's.
module Cryptol.TypeCheck.FFI
( toFFIFunType
) where
import Data.Containers.ListUtils
import Data.Either
import Cryptol.TypeCheck.FFI.Error
import Cryptol.TypeCheck.FFI.FFIType
import Cryptol.TypeCheck.SimpType
import Cryptol.TypeCheck.Type
import Cryptol.Utils.RecordMap
-- | Convert a 'Schema' to a 'FFIFunType', along with any 'Prop's that must be
-- satisfied for the 'FFIFunType' to be valid.
toFFIFunType :: Schema -> Either FFITypeError ([Prop], FFIFunType)
toFFIFunType (Forall params _ t) =
-- Remove all type synonyms and simplify the type before processing it
case go $ tRebuild' False t of
Just (Right (props, fft)) -> Right
-- Remove duplicate constraints
(nubOrd $ map (fin . TVar . TVBound) params ++ props, fft)
Just (Left errs) -> Left $ FFITypeError t $ FFIBadComponentTypes errs
Nothing -> Left $ FFITypeError t FFINotFunction
where go (TCon (TC TCFun) [argType, retType]) = Just
case toFFIType argType of
Right (ps, ffiArgType) ->
case go retType of
Just (Right (ps', ffiFunType)) -> Right
( ps ++ ps'
, ffiFunType
{ ffiArgTypes = ffiArgType : ffiArgTypes ffiFunType } )
Just (Left errs) -> Left errs
Nothing ->
case toFFIType retType of
Right (ps', ffiRetType) -> Right
( ps ++ ps'
, FFIFunType
{ ffiTParams = params
, ffiArgTypes = [ffiArgType], .. } )
Left err -> Left [err]
Left err -> Left
case go retType of
Just (Right _) -> [err]
Just (Left errs) -> err : errs
Nothing ->
case toFFIType retType of
Right _ -> [err]
Left err' -> [err, err']
go _ = Nothing
-- | Convert a 'Type' to a 'FFIType', along with any 'Prop's that must be
-- satisfied for the 'FFIType' to be valid.
toFFIType :: Type -> Either FFITypeError ([Prop], FFIType)
toFFIType t =
case t of
TCon (TC TCBit) [] -> Right ([], FFIBool)
(toFFIBasicType -> Just r) -> (\fbt -> ([], FFIBasic fbt)) <$> r
TCon (TC TCSeq) [sz, bt] ->
case toFFIBasicType bt of
Just (Right fbt) -> Right ([fin sz], FFIArray sz fbt)
Just (Left err) -> Left $ FFITypeError t $ FFIBadComponentTypes [err]
Nothing -> Left $ FFITypeError t FFIBadArrayType
TCon (TC (TCTuple _)) ts ->
case partitionEithers $ map toFFIType ts of
([], unzip -> (pss, fts)) -> Right (concat pss, FFITuple fts)
(errs, _) -> Left $ FFITypeError t $ FFIBadComponentTypes errs
TRec tMap ->
case sequence resMap of
Right resMap' -> Right $ FFIRecord <$>
recordMapAccum (\ps (ps', ft) -> (ps' ++ ps, ft)) [] resMap'
Left _ -> Left $ FFITypeError t $
FFIBadComponentTypes $ lefts $ displayElements resMap
where resMap = fmap toFFIType tMap
_ -> Left $ FFITypeError t FFIBadType
-- | Convert a 'Type' to a 'FFIBasicType', returning 'Nothing' if it isn't a
-- basic type and 'Left' if it is but there was some other issue with it.
toFFIBasicType :: Type -> Maybe (Either FFITypeError FFIBasicType)
toFFIBasicType t =
case t of
TCon (TC TCSeq) [TCon (TC (TCNum n)) [], TCon (TC TCBit) []]
| n <= 8 -> word FFIWord8
| n <= 16 -> word FFIWord16
| n <= 32 -> word FFIWord32
| n <= 64 -> word FFIWord64
| otherwise -> Just $ Left $ FFITypeError t FFIBadWordSize
where word = Just . Right . FFIWord n
TCon (TC TCFloat) [TCon (TC (TCNum e)) [], TCon (TC (TCNum p)) []]
| e == 8, p == 24 -> float FFIFloat32
| e == 11, p == 53 -> float FFIFloat64
| otherwise -> Just $ Left $ FFITypeError t FFIBadFloatSize
where float = Just . Right . FFIFloat e p
_ -> Nothing
fin :: Type -> Prop
fin t = TCon (PC PFin) [t]

View File

@ -0,0 +1,81 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Errors from typechecking foreign functions.
module Cryptol.TypeCheck.FFI.Error where
import Control.DeepSeq
import GHC.Generics
import Cryptol.TypeCheck.PP
import Cryptol.TypeCheck.Subst
import Cryptol.TypeCheck.Type
data FFITypeError = FFITypeError Type FFITypeErrorReason
deriving (Show, Generic, NFData)
data FFITypeErrorReason
= FFIBadWordSize
| FFIBadFloatSize
| FFIBadArrayType
| FFIBadComponentTypes [FFITypeError]
| FFIBadType
| FFINotFunction
deriving (Show, Generic, NFData)
instance TVars FFITypeError where
apSubst su (FFITypeError t r) = FFITypeError !$ apSubst su t !$ apSubst su r
instance TVars FFITypeErrorReason where
apSubst su r =
case r of
FFIBadWordSize -> r
FFIBadFloatSize -> r
FFIBadArrayType -> r
FFIBadComponentTypes errs -> FFIBadComponentTypes !$ apSubst su errs
FFIBadType -> r
FFINotFunction -> r
instance FVS FFITypeError where
fvs (FFITypeError t r) = fvs (t, r)
instance FVS FFITypeErrorReason where
fvs r =
case r of
FFIBadWordSize -> mempty
FFIBadFloatSize -> mempty
FFIBadArrayType -> mempty
FFIBadComponentTypes errs -> fvs errs
FFIBadType -> mempty
FFINotFunction -> mempty
instance PP (WithNames FFITypeError) where
ppPrec _ (WithNames (FFITypeError t r) names) =
nest 2 $ "Type unsupported for FFI:" $$
vcat
[ ppWithNames names t
, "Due to:"
, ppWithNames names r ]
instance PP (WithNames FFITypeErrorReason) where
ppPrec _ (WithNames r names) =
case r of
FFIBadWordSize -> vcat
[ "Unsupported word size"
, "Only words of up to 64 bits are supported" ]
FFIBadFloatSize -> vcat
[ "Unsupported Float format"
, "Only Float32 and Float64 are supported" ]
FFIBadArrayType -> vcat
[ "Unsupported sequence element type"
, "Only words or floats are supported as the element type of sequences"
]
FFIBadComponentTypes errs ->
indent 2 $ vcat $ map (ppWithNames names) errs
FFIBadType -> vcat
[ "Only Bit, words, floats, sequences of words or floats,"
, "or structs or tuples of the above are supported as FFI"
, "argument or return types"]
FFINotFunction -> "FFI binding must be a function"

View File

@ -0,0 +1,57 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
-- | This module defines a nicer intermediate representation of Cryptol types
-- allowed for the FFI, which the typechecker generates then stores in the AST.
-- This way the FFI evaluation code does not have to examine the raw type
-- signatures again.
module Cryptol.TypeCheck.FFI.FFIType where
import Control.DeepSeq
import GHC.Generics
import Cryptol.TypeCheck.Type
import Cryptol.Utils.Ident
import Cryptol.Utils.RecordMap
-- | Type of a foreign function.
data FFIFunType = FFIFunType
{ -- | Note: any type variables within this function type must be bound here.
ffiTParams :: [TParam]
, ffiArgTypes :: [FFIType]
, ffiRetType :: FFIType }
deriving (Show, Generic, NFData)
-- | Type of a value that can be passed to or returned from a foreign function.
data FFIType
= FFIBool
| FFIBasic FFIBasicType
| FFIArray
Type -- ^ Size (should be of kind @\#@)
FFIBasicType -- ^ Element type
| FFITuple [FFIType]
| FFIRecord (RecordMap Ident FFIType)
deriving (Show, Generic, NFData)
-- | Types which can be elements of FFI sequences.
data FFIBasicType
= FFIWord
Integer -- ^ The size of the Cryptol type
FFIWordSize -- ^ The machine word size that it corresponds to
| FFIFloat
Integer -- ^ Exponent
Integer -- ^ Precision
FFIFloatSize -- ^ The machine float size that it corresponds to
deriving (Show, Generic, NFData)
data FFIWordSize
= FFIWord8
| FFIWord16
| FFIWord32
| FFIWord64
deriving (Show, Generic, NFData)
data FFIFloatSize
= FFIFloat32
| FFIFloat64
deriving (Show, Generic, NFData)

View File

@ -52,6 +52,8 @@ import Cryptol.TypeCheck.Kind(checkType,checkSchema,checkTySyn,
import Cryptol.TypeCheck.Instantiate
import Cryptol.TypeCheck.Subst (listSubst,apSubst,(@@),isEmptySubst)
import Cryptol.TypeCheck.Unify(rootPath)
import Cryptol.TypeCheck.FFI
import Cryptol.TypeCheck.FFI.FFIType
import Cryptol.Utils.Ident
import Cryptol.Utils.Panic(panic)
import Cryptol.Utils.RecordMap
@ -66,7 +68,8 @@ import Data.Maybe(isJust, fromMaybe, mapMaybe)
import Data.Ratio(numerator,denominator)
import Data.Traversable(forM)
import Data.Function(on)
import Control.Monad(zipWithM,unless,foldM,forM_,mplus, join)
import Control.Monad(zipWithM, unless, foldM, forM_, mplus, zipWithM,
unless, foldM, forM_, mplus, when)
import Data.Bifunctor (Bifunctor(second))
@ -839,7 +842,12 @@ guessType exprMap b@(P.Bind { .. }) =
case bSignature of
Just s ->
do s1 <- checkSchema AllowWildCards s
do let wildOk = case thing bDef of
P.DForeign {} -> NoWildCards
P.DPrim -> NoWildCards
P.DExpr {} -> AllowWildCards
P.DPropGuards {} -> NoWildCards
s1 <- checkSchema wildOk s
return ((name, ExtVar (fst s1)), Left (checkSigB b s1))
Nothing
@ -933,8 +941,9 @@ generalize bs0 gs0 =
genE e = foldr ETAbs (foldr EProofAbs (apSubst su e) qs) asPs
genB d = d { dDefinition = case dDefinition d of
DExpr e -> DExpr (genE e)
DPrim -> DPrim
DExpr e -> DExpr (genE e)
DPrim -> DPrim
DForeign t -> DForeign t
, dSignature = Forall asPs qs
$ apSubst su $ sType $ dSignature d
}
@ -956,6 +965,8 @@ checkMonoB b t =
P.DPrim -> panic "checkMonoB" ["Primitive with no signature?"]
P.DForeign -> panic "checkMonoB" ["Foreign with no signature?"]
P.DExpr e ->
do let nm = thing (P.bName b)
let tGoal = WithSource t (DefinitionOf nm) (getLoc b)
@ -993,6 +1004,37 @@ checkSigB b (Forall as asmps0 t0, validSchema) =
, dDoc = P.bDoc b
}
P.DForeign -> do
let loc = getLoc b
name' = thing $ P.bName b
src = DefinitionOf name'
inRangeMb loc do
-- Ensure all type params are of kind #
forM_ as \a ->
when (tpKind a /= KNum) $
recordErrorLoc loc $ UnsupportedFFIKind src a $ tpKind a
withTParams as do
ffiFunType <-
case toFFIFunType (Forall as asmps0 t0) of
Right (props, ffiFunType) -> ffiFunType <$ do
ffiGoals <- traverse (newGoal (CtFFI name')) props
proveImplication True (Just name') as asmps0 $
validSchema ++ ffiGoals
Left err -> do
recordErrorLoc loc $ UnsupportedFFIType src err
-- Just a placeholder type
pure FFIFunType
{ ffiTParams = as, ffiArgTypes = []
, ffiRetType = FFITuple [] }
pure Decl { dName = thing (P.bName b)
, dSignature = Forall as asmps0 t0
, dDefinition = DForeign ffiFunType
, dPragmas = P.bPragmas b
, dInfix = P.bInfix b
, dFixity = P.bFixity b
, dDoc = P.bDoc b
}
P.DExpr e0 ->
inRangeMb (getLoc b) $
withTParams as $ do
@ -1013,7 +1055,6 @@ checkSigB b (Forall as asmps0 t0, validSchema) =
withTParams as $ do
asmps1 <- applySubstPreds asmps0
t1 <- applySubst t0
-- Checking each guarded case is the same as checking a DExpr, except
-- that the guarding assumptions are added first.
let checkPropGuardCase :: ([P.Prop Name], P.Expr Name) -> InferM ([Prop], Expr)
@ -1028,7 +1069,7 @@ checkSigB b (Forall as asmps0 t0, validSchema) =
second concat . unzip <$>
checkPropGuard mb_rng `traverse` guards0
-- try to prove all goals
su <- proveImplication (Just name) as (asmps1 <> guards1) goals
su <- proveImplication True (Just name) as (asmps1 <> guards1) goals
extendSubst su
-- Preprends the goals to the constraints, because these must be
-- checked first before the rest of the constraints (during
@ -1076,21 +1117,21 @@ checkSigB b (Forall as asmps0 t0, validSchema) =
panicInvalid = tcPanic "checkSigB"
[ "This type shouldn't be a valid type constraint: " ++
"`" ++ pretty prop ++ "`"]
negateSimpleNumProps :: [Prop] -> [[Prop]]
negateSimpleNumProps props = do
prop <- props
maybe mempty pure (negateSimpleNumProp prop)
toGoal :: Prop -> Goal
toGoal prop =
toGoal :: Prop -> Goal
toGoal prop =
Goal
{ goalSource = CtPropGuardsExhaustive name
, goalRange = srcRange $ P.bName b
, goal = prop }
canProve :: [Prop] -> [Goal] -> InferM Bool
canProve asmps goals = isRight <$>
canProve asmps goals = isRight <$>
tryProveImplication (Just name) as asmps goals
-- Try to prove that the first guard will be satisfied. If cannot,
@ -1109,14 +1150,14 @@ checkSigB b (Forall as asmps0 t0, validSchema) =
checkExhaustive :: [Prop] -> [[Prop]] -> InferM Bool
checkExhaustive _asmps [] = pure False -- empty GuardProps
checkExhaustive asmps [guard] = do
canProve asmps (toGoal <$> guard)
checkExhaustive asmps (guard : guards) =
canProve asmps (toGoal <$> guard) >>= \case
canProve asmps (toGoal <$> guard)
checkExhaustive asmps (guard : guards) =
canProve asmps (toGoal <$> guard) >>= \case
True -> pure True
False -> and <$> mapM
(\asmps' -> checkExhaustive (asmps <> asmps') guards)
(negateSimpleNumProps guard)
checkExhaustive asmps1 (fst <$> cases1) >>= \case
True ->
-- proved exhaustive
@ -1125,7 +1166,7 @@ checkSigB b (Forall as asmps0 t0, validSchema) =
-- didn't prove exhaustive i.e. none of the guarding props
-- necessarily hold
recordWarning (NonExhaustivePropGuards name)
return Decl
{ dName = name
, dSignature = Forall as asmps1 t1
@ -1178,7 +1219,7 @@ checkSigB b (Forall as asmps0 t0, validSchema) =
-- includes asmpsSign for the sake of implication, but doesn't actually
-- include them in the resulting asmps
su <- proveImplication (Just (thing (P.bName b))) as (asmpsSign <> asmps2) stay
su <- proveImplication True (Just (thing (P.bName b))) as (asmpsSign <> asmps2) stay
extendSubst su
let asmps = concatMap pSplitAnd (apSubst su asmps2)
@ -1274,8 +1315,8 @@ checkDecl isTopLevel d mbDoc =
checkParameterFun :: P.ParameterFun Name -> InferM ModVParam
checkParameterFun x =
do (s,gs) <- checkSchema NoWildCards (P.pfSchema x)
su <- proveImplication (Just (thing (P.pfName x)))
(sVars s) (sProps s) gs
su <- proveImplication False (Just (thing (P.pfName x)))
(sVars s) (sProps s) gs
unless (isEmptySubst su) $
panic "checkParameterFun" ["Subst not empty??"]
let n = thing (P.pfName x)

View File

@ -221,6 +221,8 @@ data ConstraintSource
| CtPattern TypeSource -- ^ Constraints arising from type-checking patterns
| CtModuleInstance ModName -- ^ Instantiating a parametrized module
| CtPropGuardsExhaustive Name -- ^ Checking that a use of prop guards is exhastive
| CtFFI Name -- ^ Constraints on a foreign declaration required
-- by the FFI (e.g. sequences must be finite)
deriving (Show, Generic, NFData)
selSrc :: Selector -> TypeSource
@ -249,6 +251,7 @@ instance TVars ConstraintSource where
CtPattern _ -> src
CtModuleInstance _ -> src
CtPropGuardsExhaustive _ -> src
CtFFI _ -> src
instance FVS Goal where
@ -356,6 +359,7 @@ instance PP ConstraintSource where
CtPattern ad -> "checking a pattern:" <+> pp ad
CtModuleInstance n -> "module instantiation" <+> pp n
CtPropGuardsExhaustive n -> "exhaustion check for prop guards used in defining" <+> pp n
CtFFI f -> "declaration of foreign function" <+> pp f
ppUse :: Expr -> Doc
ppUse expr =

View File

@ -95,6 +95,7 @@ instance ShowParseable Decl where
instance ShowParseable DeclDef where
showParseable DPrim = text (show DPrim)
showParseable (DForeign t) = text (show $ DForeign t)
showParseable (DExpr e) = parens (text "DExpr" $$ showParseable e)
instance ShowParseable DeclGroup where

View File

@ -404,6 +404,10 @@ checkDecl checkSig d =
do when checkSig $ checkSchema $ dSignature d
return (dName d, dSignature d)
DForeign _ ->
do when checkSig $ checkSchema $ dSignature d
return (dName d, dSignature d)
DExpr e ->
do let s = dSignature d
when checkSig $ checkSchema s

View File

@ -38,6 +38,7 @@ import Cryptol.Utils.Patterns(matchMaybe)
import Control.Applicative ((<|>))
import Control.Monad(mzero)
import Data.Containers.ListUtils (nubOrd)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Set ( Set )
@ -266,20 +267,21 @@ proveModuleTopLevel =
cs <- getParamConstraints
case cs of
[] -> addGoals gs1
_ -> do su2 <- proveImplication Nothing [] [] gs1
_ -> do su2 <- proveImplication False Nothing [] [] gs1
extendSubst su2
-- | Prove an implication, and return any improvements that we computed.
-- Records errors, if any of the goals couldn't be solved.
proveImplication :: Maybe Name -> [TParam] -> [Prop] -> [Goal] -> InferM Subst
proveImplication lnam as ps gs =
proveImplication :: Bool -> Maybe Name ->
[TParam] -> [Prop] -> [Goal] -> InferM Subst
proveImplication dedupErrs lnam as ps gs =
do evars <- varsWithAsmps
solver <- getSolver
extraAs <- (map mtpParam . Map.elems) <$> getParamTypes
extra <- map thing <$> getParamConstraints
(mbErr,su) <- io (proveImplicationIO solver lnam evars
(mbErr,su) <- io (proveImplicationIO solver dedupErrs lnam evars
(extraAs ++ as) (extra ++ ps) gs)
case mbErr of
Right ws -> mapM_ recordWarning ws
@ -301,7 +303,7 @@ tryProveImplication lnam as ps gs =
extraAs <- (map mtpParam . Map.elems) <$> getParamTypes
extra <- map thing <$> getParamConstraints
(mbErr,su) <- io (proveImplicationIO solver lnam evars
(mbErr,su) <- io (proveImplicationIO solver False lnam evars
(extraAs ++ as) (extra ++ ps) gs)
case mbErr of
Left errs -> pure . Left $ do
@ -311,6 +313,7 @@ tryProveImplication lnam as ps gs =
return su
proveImplicationIO :: Solver
-> Bool -- ^ Whether to remove duplicate goals in errors
-> Maybe Name -- ^ Checking this function
-> Set TVar -- ^ These appear in the env., and we should
-- not try to default them
@ -318,8 +321,8 @@ proveImplicationIO :: Solver
-> [Prop] -- ^ Assumed constraint
-> [Goal] -- ^ Collected constraints
-> IO (Either [Error] [Warning], Subst)
proveImplicationIO _ _ _ _ [] [] = return (Right [], emptySubst)
proveImplicationIO s f varsInEnv ps asmps0 gs0 =
proveImplicationIO _ _ _ _ _ [] [] = return (Right [], emptySubst)
proveImplicationIO s dedupErrs f varsInEnv ps asmps0 gs0 =
do let ctxt = buildSolverCtxt asmps
res <- quickSolverIO ctxt gs
case res of
@ -339,7 +342,7 @@ proveImplicationIO s f varsInEnv ps asmps0 gs0 =
return (Left (err gs3:errs), su) -- XXX: Old?
(_,newGs,newSu,ws,errs) ->
do let su1 = newSu @@ su
(res1,su2) <- proveImplicationIO s f varsInEnv ps
(res1,su2) <- proveImplicationIO s dedupErrs f varsInEnv ps
(apSubst su1 asmps0) newGs
let su3 = su2 @@ su1
case res1 of
@ -353,7 +356,7 @@ proveImplicationIO s f varsInEnv ps asmps0 gs0 =
$ DelayedCt { dctSource = f
, dctForall = ps
, dctAsmps = asmps0
, dctGoals = us
, dctGoals = if dedupErrs then nubOrd us else us
}

View File

@ -408,8 +408,9 @@ instance TVars Decl where
in d { dSignature = sig', dDefinition = def' }
instance TVars DeclDef where
apSubst su (DExpr e) = DExpr !$ (apSubst su e)
apSubst _ DPrim = DPrim
apSubst su (DExpr e) = DExpr !$ (apSubst su e)
apSubst _ DPrim = DPrim
apSubst _ (DForeign t) = DForeign t
instance TVars Module where
apSubst su m =

View File

@ -22,6 +22,7 @@ module Cryptol.Utils.RecordMap
, canonicalFields
, displayFields
, recordElements
, displayElements
, fieldSet
, recordFromFields
, recordFromFieldsErr
@ -90,6 +91,11 @@ recordElements = map snd . canonicalFields
canonicalFields :: RecordMap a b -> [(a,b)]
canonicalFields = Map.toList . recordMap
-- | Retrieve the elements of the record in display order
-- of the field names.
displayElements :: (Show a, Ord a) => RecordMap a b -> [b]
displayElements = map snd . displayFields
-- | Return a list of field/value pairs in display order.
displayFields :: (Show a, Ord a) => RecordMap a b -> [(a,b)]
displayFields r = map find (displayOrder r)

View File

@ -7,6 +7,8 @@
-- Portability : portable
-- {-# LANGUAGE Safe #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE Safe #-}
module Cryptol.Version (
commitHash
@ -39,6 +41,9 @@ displayVersion putLn = do
putLn ("Cryptol " ++ ver)
putLn ("Git commit " ++ commitHash)
putLn (" branch " ++ commitBranch ++ dirtyLab)
#ifdef FFI_ENABLED
putLn "FFI enabled"
#endif
where
dirtyLab | commitDirty = " (non-committed files present during build)"
| otherwise = ""

2
tests/.gitignore vendored
View File

@ -1 +1,3 @@
output
*.so
*.dylib

28
tests/ffi/Makefile Normal file
View File

@ -0,0 +1,28 @@
CFLAGS += -Wall -Werror
# For each C file foo.c, we make a phony target foo, then depending on the OS
# map that to either foo.dylib or foo.so.
CFILES = $(wildcard *.c)
TARGETS = $(CFILES:.c=)
all: $(TARGETS)
.PHONY: all clean $(TARGETS)
ifeq ($(shell uname),Darwin)
EXT = .dylib
else
EXT = .so
endif
$(TARGETS): %: %$(EXT)
%.dylib: %.c
$(CC) $(CFLAGS) -dynamiclib $< -o $@
%.so: %.c
$(CC) $(CFLAGS) -fPIC -shared $< -o $@
clean:
rm *$(EXT)

1
tests/ffi/ffi-reload.cry Normal file
View File

@ -0,0 +1 @@
foreign test : () -> Bit

10
tests/ffi/ffi-reload.icry Normal file
View File

@ -0,0 +1,10 @@
:! echo '#include <stdint.h>\nuint8_t test() { return 0; }' > ffi-reload.c
:! make -s ffi-reload
:l ffi-reload.cry
test ()
:! sleep 1
:! sed -i.bak 's/return 0/return 1/' ffi-reload.c
:! make -s ffi-reload
:r
test ()
:! rm ffi-reload.c ffi-reload.c.bak

View File

@ -0,0 +1,7 @@
Loading module Cryptol
Loading module Cryptol
Loading module Main
False
Loading module Cryptol
Loading module Main
True

View File

@ -0,0 +1,10 @@
#include <stddef.h>
#include <stdint.h>
uint8_t f(size_t n, uint8_t *p) {
return n == 0;
}
uint8_t g(uint8_t x) {
return x;
}

View File

@ -0,0 +1,2 @@
foreign f : {n} (fin n, n >= 2^^64) => [n - 2^^64][8] -> Bit
foreign g : Bit -> Bit

View File

@ -0,0 +1,9 @@
:! make -s ffi-runtime-errors
:l ffi-runtime-errors.cry
f []
:prove g
:sat g
:safe g
:eval g

View File

@ -0,0 +1,28 @@
Loading module Cryptol
Loading module Cryptol
Loading module Main
numeric type argument to foreign function is too large: 18446744073709551616
in type parameter n`899 of function Main::f
type arguments must fit in a C `size_t`
-- Backtrace --
Main::f called at ffi-runtime-errors.icry:4:1--4:2
cannot call foreign function Main::g
FFI calls are not supported in this context
If you are trying to evaluate an expression, try rebuilding
Cryptol with FFI support enabled.
cannot call foreign function Main::g
FFI calls are not supported in this context
If you are trying to evaluate an expression, try rebuilding
Cryptol with FFI support enabled.
cannot call foreign function Main::g
FFI calls are not supported in this context
If you are trying to evaluate an expression, try rebuilding
Cryptol with FFI support enabled.
cannot call foreign function Main::g
FFI calls are not supported in this context
If you are trying to evaluate an expression, try rebuilding
Cryptol with FFI support enabled.

View File

@ -0,0 +1,11 @@
import Float
foreign badWordSizes : [65] -> [128]
foreign badFloatSizes : Float16 -> Float128
foreign badArrayTypes : {n} (fin n) => [n]Bit -> [n]([16], [16]) -> [n][4][8]
foreign badTypes : Integer -> Z 3
foreign notFunction : [32]
foreign badKind : {t} t -> [32]
foreign noFin : {n} [n][32] -> [n * 2][32]
foreign infSeq : [inf][32] -> Bit
foreign wildcard : { x : [8], y : Float32 } -> _

View File

@ -0,0 +1 @@
:l ffi-type-errors.cry

View File

@ -0,0 +1,102 @@
Loading module Cryptol
Loading module Cryptol
Loading module Float
Loading module Main
[error] at ffi-type-errors.cry:3:9--3:37:
Type unsupported for FFI:
[65] -> [128]
Due to:
Type unsupported for FFI:
[65]
Due to:
Unsupported word size
Only words of up to 64 bits are supported
Type unsupported for FFI:
[128]
Due to:
Unsupported word size
Only words of up to 64 bits are supported
When checking the type of 'Main::badWordSizes'
[error] at ffi-type-errors.cry:4:9--4:44:
Type unsupported for FFI:
Float 5 11 -> Float 15 113
Due to:
Type unsupported for FFI:
Float 5 11
Due to:
Unsupported Float format
Only Float32 and Float64 are supported
Type unsupported for FFI:
Float 15 113
Due to:
Unsupported Float format
Only Float32 and Float64 are supported
When checking the type of 'Main::badFloatSizes'
[error] at ffi-type-errors.cry:5:9--5:78:
Type unsupported for FFI:
[n`969] -> [n`969]([16], [16]) -> [n`969][4][8]
Due to:
Type unsupported for FFI:
[n`969]
Due to:
Unsupported sequence element type
Only words or floats are supported as the element type of sequences
Type unsupported for FFI:
[n`969]([16], [16])
Due to:
Unsupported sequence element type
Only words or floats are supported as the element type of sequences
Type unsupported for FFI:
[n`969][4][8]
Due to:
Unsupported sequence element type
Only words or floats are supported as the element type of sequences
When checking the type of 'Main::badArrayTypes'
[error] at ffi-type-errors.cry:6:9--6:34:
Type unsupported for FFI:
Integer -> Z 3
Due to:
Type unsupported for FFI:
Integer
Due to:
Only Bit, words, floats, sequences of words or floats,
or structs or tuples of the above are supported as FFI
argument or return types
Type unsupported for FFI:
Z 3
Due to:
Only Bit, words, floats, sequences of words or floats,
or structs or tuples of the above are supported as FFI
argument or return types
When checking the type of 'Main::badTypes'
[error] at ffi-type-errors.cry:7:9--7:27:
Type unsupported for FFI:
[32]
Due to:
FFI binding must be a function
When checking the type of 'Main::notFunction'
[error] at ffi-type-errors.cry:8:9--8:32:
Kind of type variable unsupported for FFI:
t`970 : *
Only type variables of kind # are supported
When checking the type of 'Main::badKind'
[error] at ffi-type-errors.cry:9:9--9:43:
Failed to validate user-specified signature.
in the definition of 'Main::noFin', at ffi-type-errors.cry:9:9--9:14,
we need to show that
for any type n
the following constraints hold:
• fin n
arising from
declaration of foreign function Main::noFin
at ffi-type-errors.cry:9:9--9:43
[error] at ffi-type-errors.cry:10:9--10:34:
• Unsolvable constraint:
fin inf
arising from
declaration of foreign function Main::infSeq
at ffi-type-errors.cry:10:9--10:34
[error] at ffi-type-errors.cry:11:48--11:49:
Wild card types are not allowed in this context
(e.g., they cannot be used in type synonyms or FFI declarations).

118
tests/ffi/test-ffi.c Normal file
View File

@ -0,0 +1,118 @@
#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <stddef.h>
uint8_t add8(uint8_t x, uint8_t y) {
return x + y;
}
uint16_t sub16(uint16_t x, uint16_t y) {
return x - y;
}
uint32_t mul32(uint32_t x, uint32_t y) {
return x * y;
}
uint64_t div64(uint64_t x, uint64_t y) {
return x / y;
}
uint8_t extendInput(uint8_t x) {
return x;
}
uint8_t maskOutput(uint8_t x) {
return x;
}
uint8_t noBits(uint8_t zero) {
assert(zero == 0);
return 1;
}
uint8_t not(uint8_t x) {
return !x;
}
float addFloat32(float x, float y) {
return x + y;
}
double subFloat64(double x, double y) {
return x - y;
}
float specialFloats(uint8_t select) {
switch (select) {
case 0:
return INFINITY;
case 1:
return -INFINITY;
case 2:
return NAN;
case 3:
return -0.0f;
}
return 0;
}
uint8_t usesTypeSynonym(uint32_t x, float y) {
return x == y;
}
uint32_t sum10(uint32_t *xs) {
uint32_t sum = 0;
for (unsigned i = 0; i < 10; ++i) {
sum += xs[i];
}
return sum;
}
void reverse5(double *in, double *out) {
for (unsigned i = 0; i < 5; ++i) {
out[i] = in[4 - i];
}
}
void compoundTypes(uint32_t n, uint16_t x, uint32_t *y, uint32_t *z,
uint16_t *a_0, uint16_t *a_1, uint32_t *c, uint8_t *d, uint8_t *e) {
*a_0 = n >> 16;
*a_1 = n;
for (unsigned i = 0; i < 3; ++i) {
c[i] = y[i];
}
for (unsigned i = 0; i < 5; ++i) {
c[i + 3] = z[i];
}
*d = x >> 5;
*e = x;
}
uint64_t typeToValue(size_t n) {
return n;
}
uint32_t sumPoly(size_t n, uint32_t *xs) {
uint32_t sum = 0;
for (size_t i = 0; i < n; ++i) {
sum += xs[i];
}
return sum;
}
void inits(size_t n, uint8_t *in, uint8_t *out) {
for (unsigned i = 1; i <= n; ++i) {
for (unsigned j = 0; j < i; ++j) {
*out++ = in[j];
}
}
}
void zipMul3(size_t n, size_t m, size_t p, float *xs, float *ys, float *zs,
float *out) {
for (size_t i = 0; i < n && i < m && i < p; ++i) {
out[i] = xs[i] * ys[i] * zs[i];
}
}

40
tests/ffi/test-ffi.cry Normal file
View File

@ -0,0 +1,40 @@
import Float
// Basic integral types
foreign add8 : [8] -> [8] -> [8]
foreign sub16 : [16] -> [16] -> [16]
foreign mul32 : [32] -> [32] -> [32]
foreign div64 : [64] -> [64] -> [64]
// Non-machine integer sizes
foreign extendInput : [3] -> [8]
foreign maskOutput : [8] -> [3]
foreign noBits : [0] -> [0]
// Bit
foreign not : Bit -> Bit
// Float
foreign addFloat32 : Float32 -> Float32 -> Float32
foreign subFloat64 : Float64 -> Float64 -> Float64
foreign specialFloats : [2] -> Float32
// Type synonyms
type Word32 = [32]
type MyFunc = Word32 -> Float32 -> Bit
foreign usesTypeSynonym : MyFunc
// Sequences
foreign sum10 : [10]Word32 -> Word32
foreign reverse5 : [5]Float64 -> [5]Float64
// Tuples and records
foreign compoundTypes : ([32], { x : [10], y : [3][20] }) -> { z : [5][20] }
-> { a : ([16], [16]), b : { c : [8][20], d : [5], e : [5] } }
// Polymorphic sizes
foreign typeToValue : {n} (fin n) => () -> [64]
foreign sumPoly : {n} (fin n) => [n]Word32 -> Word32
foreign inits : {n} (fin n) => [n][8] -> [n * (n + 1) / 2][8]
foreign zipMul3 : {n, m, p} (fin n, fin m, fin p) =>
[n]Float32 -> [m]Float32 -> [p]Float32 -> [min n (min m p)]Float32

34
tests/ffi/test-ffi.icry Normal file
View File

@ -0,0 +1,34 @@
:! make -s test-ffi
:l test-ffi.cry
add8 1 2
sub16 12345 6789
mul32 123456 7890
div64 12345678901234567890 987654321
extendInput 7
maskOutput 255
noBits 0
not True
addFloat32 12.34 56.78
subFloat64 1234.5678 9012.3456
specialFloats 0
specialFloats 1
specialFloats 2
specialFloats 3
usesTypeSynonym 42 42
sum10 [1..10]
reverse5 [0x1.23, -0x3.45, 0x6.78, -0x9.0a, 0xb.cd]
compoundTypes (0x12345678, {y = [10, 20, 30], x = 300}) {z = [40, 50, 60, 70, 80]}
typeToValue`{0x12345678deadbeef} ()
sumPoly []
sumPoly [1..10]
sumPoly [1..10000]
inits [1..5]
zipMul3 [1, 2, 3] [3, 4, 5, 6] [6, 7, 8, 9, 10]

View File

@ -0,0 +1,33 @@
Loading module Cryptol
Loading module Cryptol
Loading module Float
Loading module Main
0x03
0x15b4
0x3a0f1880
0x00000002e90edc8f
0x07
0x7
0x0
False
0x45.1eb8
-0x1e61.c71de69ad5
fpPosInf
fpNegInf
fpNaN
-0.0
True
0x00000037
[0xb.cd, -0x9.0a, 0x6.78, -0x3.45, 0x1.23]
{a = (0x1234, 0x5678),
b = {c = [0x0000a, 0x00014, 0x0001e, 0x00028, 0x00032, 0x0003c,
0x00046, 0x00050],
d = 0x09,
e = 0x0c}}
0x12345678deadbeef
0x00000000
0x00000037
0x02fb0408
[0x01, 0x01, 0x02, 0x01, 0x02, 0x03, 0x01, 0x02, 0x03, 0x04, 0x01,
0x02, 0x03, 0x04, 0x05]
[0x12.0, 0x38.0, 0x78.0]

View File

@ -77,7 +77,7 @@ Loading module Main
[error] at tc-errors-4.cry:1:10--1:11:
Wild card types are not allowed in this context
(e.g., they cannot be used in type synonyms).
(e.g., they cannot be used in type synonyms or FFI declarations).
Loading module Cryptol
Loading module Main