cryptol/docs/ffi-presentation.md

314 lines
5.8 KiB
Markdown
Raw Normal View History

2022-09-09 04:28:30 +03:00
---
title: Cryptol FFI
author: Bretton
date: 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?
```cry
foreign add : [32] -> [32] -> [32]
```
```c
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?
- Must be `fin`
---
# Supported ~~types~~ kinds?
## Numeric type arguments
Cryptol | C
--- | ---
\# | size_t
- 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
Cryptol | C
--- | ---
Ti | Ui
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
```cry
foreign f : {n} (fin n) => [n][10] -> {a : Bit, b : [64]} -> (Float64, [n + 1][20])
```
```c
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
```cry
foreign print : {n} (fin n) => [n][8] -> ()
```
```c
void print(size_t n, char *s) {
printf("%.*s\n", (int) n, s);
}
```
```
Main> print "Hello world!"
Hello world!
()
```
```cry
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
```hs
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
- `import M ((<+>))`
- Fixed fixity (fixedity?) of prefix operators
- `-x**2` parses as `-(x**2)` not `(-x)**2`
---
# Thank you!
- Iavor, Eddy, Ryan
# Questions?
- Why is the Cryptol logo a water drop