title |
author |
date |
Cryptol FFI |
Bretton |
2022-09-02 |
Cryptol FFI
- Call functions written in C
Why?
- Performance
- Use existing code in C
- Do things that Cryptol can't do?
What does it look like?
foreign add : [32] -> [32] -> [32]
uint32_t add(uint32_t x, uint32_t y) {
return x + y;
}
Main> add 1 2
0x00000003
How do you use it?
+-------------+
| Example.cry | ------------------------+ +---------------Cryptol---------------+
+-------------+ | | Cryptol> :l Example.cry |
| | Loading module Cryptol |
+-----------+ +---------------+ +------>| Loading module Example |
| Example.c | =====> | Example.so | -------->| Loading dynamic library Example.so |
+-----------+ cc | Example.dylib | dlopen | Example> add 1 2 |
+---------------+ | 0x00000003 |
+-------------------------------------+
- Find shared library with same name as Cryptol file
- Dynamically load with
dlopen
- Bind each
foreign
function with dlsym
- Windows not supported yet
- Compile it yourself (for now...)
cc -fPIC -shared Example.c -o Example.so
cc -dynamiclib Example.c -o Example.dylib
How does it work?
- Since Cryptol is interpreted, number/types of arguments to foreign functions are not known statically
libffi
calls functions with the right calling conventions given interface at runtime
- Rant about
libffi
in Haskell
Supported types
Basic types
Cryptol |
C |
Bit |
uint8_t |
[K]Bit, 0 <= K <= 8 |
uint8_t |
[K]Bit, 8 < K <= 16 |
uint16_t |
[K]Bit, 16 < K <= 32 |
uint32_t |
[K]Bit, 32 < K <= 64 |
uint64_t |
Float32 |
float |
Float64 |
double |
Supported types
Multiple arguments?
Cryptol |
C |
A -> B -> ... -> Z |
Z' (A', B', ...) |
Supported types
More complex types
- So far it has been pretty simple
- How about sequences? I guess we need arrays
- Who allocates the memory?
- Who owns the memory?
- How are they passed vs returned?
Supported types
Sequences
Cryptol |
C |
[n1][n2]...[nk]T |
U* |
where
T |
U |
[K]Bit, 0 <= K <= 8 |
uint8_t |
[K]Bit, 8 < K <= 16 |
uint16_t |
[K]Bit, 16 < K <= 32 |
uint32_t |
[K]Bit, 32 < K <= 64 |
uint64_t |
Float32 |
float |
Float64 |
double |
- Cryptol allocates and owns all memory
- Sequence as Cryptol parameter:
const U*
C parameter
- Sequence as Cryptol return type:
U*
C output parameter
- Passed after Cryptol parameters
- Return type of C function is
void
- Multidimensional sequences: flatten (except the [K]Bit part)
- How about n1, n2, ..., nk?
Supported types kinds?
Numeric type arguments
- Must be
fin
- Type arguments are passed before any value arguments
Supported types
Tuples and records as parameters
- Passed as separate arguments
Cryptol |
C (multiple!) |
(T1, T2, ..., Tn) |
U1, U2, ..., Un |
{s1: T1, s2: T2, ..., sn: Tn} |
U1, U2, ..., Un |
where
Note:
- Order of record fields is however it's written in the type in the source code
- Uncurried and curried Cryptol functions correspond to the same C function
- () corresponds to no arguments at all
- Can be nested
Supported types
Tuples and records as return types
- Returned as separate output parameters
- This means the component types need to become pointers
- Except for sequences which are already pointers
Cryptol |
C (multiple!) |
(T1, T2, ..., Tn) |
V1, V2, ..., Vn |
{s1: T1, s2: T2, ..., sn: Tn} |
V1, V2, ..., Vn |
where
Cryptol parameter |
C |
Ti |
Ui |
and Vi = if Ui is a pointer type then Ui else Ui*
Note:
- Return type of C function is
void
- () corresponds to no return value and no output arguments
- Can be nested
Supported types
Type synonyms
- Type synonyms are expanded before applying the previous rules
Examples
foreign f : {n} (fin n) => [n][10] -> {a : Bit, b : [64]} -> (Float64, [n + 1][20])
void f(size_t n, uint16_t *in0, uint8_t in1, uint64_t in2,
double *out0, uint32_t *out1);
- More examples in FFI tests
Evaluation
- All arguments are fully evaluated
- Result will be fully evaluated too (of course)
Questionable uses of FFI
foreign print : {n} (fin n) => [n][8] -> ()
void print(size_t n, char *s) {
printf("%.*s\n", (int) n, s);
}
Main> print "Hello world!"
Hello world!
()
foreign launchMissiles : ()
etc
- Cryptol functions should be pure
- Beware of laziness
AES example
- Replacement for
SuiteB
module using AES-NI for AES functions
- ~5.5x faster
- But could actually be much faster, if not limited to 1 block
Somewhat interesting(?) implementation details
ForeignPtr
for dlclose
- Interesting types in FFI marshalling
getMarshalBasicArg :: FFIBasicType ->
(forall a. FFIArg a => (GenValue Concrete -> Eval a) -> b) -> b
data GetRet = GetRet
{ getRetAsValue :: forall a. FFIRet a => IO a
, getRetAsOutArgs :: [SomeFFIArg] -> IO () }
Future work
- Automatically generate header files from
foreign
declarations
- Support Windows
- Support Integer/Z
- Cryptol implementation of
foreign
functions + SAW integration
Other stuff (not FFI)
:time
command for benchmarking evaluation of Cryptol expressions
- Evaluates it a bunch of times and reports the average time
- Import specific operators
- Fixed fixity (fixedity?) of prefix operators
-x**2
parses as -(x**2)
not (-x)**2
Thank you!
Questions?
- Why is the Cryptol logo a water drop