Skip to content

Commit

Permalink
Merge pull request #167 from sine-fdn/literal-serde-docs
Browse files Browse the repository at this point in the history
Serde docs + ABNF grammar
  • Loading branch information
fkettelhoit authored Jan 8, 2025
2 parents 675807e + 6829fe6 commit ba556d3
Show file tree
Hide file tree
Showing 13 changed files with 349 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ jobs:
~/.rustup
key: ${{ env.cache-name }}-${{ hashFiles('**/Cargo.toml') }}
- run: cargo build --features="bin"
- run: cargo test
- run: cargo test --features="serde"
- run: cargo clippy -- -Dwarnings
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ plot = ["plotters"]
[dev-dependencies]
quickcheck = "1"
quickcheck_macros = "1"
serde_json = "1.0.134"
serde = { version = "1.0", features = ["derive"] }
3 changes: 2 additions & 1 deletion garble_docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [If/Else and Pattern Matching](./guide/control_flow.md)
- [For Loops and For-Join Loops](./guide/loops.md)
- [Const Parameters](./guide/const.md)
- [Circuits and Termination](./guide/computational_model.md)
- [Circuits and Termination](./computational_model.md)
- [MPC Engine Integration](./integration.md)
- [(De-)Serializing Garble Values](./serde.md)
- [Contributing](./contributing.md)
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Circuits and Termination

Garble programs are Boolean _circuits_ consisting of a graph of logic gates, not a sequentially executed program of instructions on a von Neumann architecture with main memory and CPU. This has deep consequences for the programming style that leads to efficient Garble programs, with programs that would be efficient in "normal" programming languages resulting in highly inefficient circuits and vice versa.
Garble programs are Boolean _circuits_ consisting of a graph of logic gates, not sequentially executed programs of instructions on a von Neumann architecture with main memory and CPU. This has deep consequences for the programming style that leads to efficient Garble programs, with programs that would be efficient in "normal" programming languages resulting in highly inefficient circuits and vice versa.

## Copying is Free

One example has already been mentioned: Copying whole arrays in Garble is essentially free, because arrays (and their elements) are just a collection of output wires from a bunch of Boolean logic gates. Duplicating these wires does not increase the complexity of the circuit, because no additional logic gates are required.
One example has already been mentioned in the context of [arrays](./guide/data_types.md#arrays-and-ranges): Copying whole arrays in Garble is essentially free, because arrays (and their elements) are just a collection of output wires from a bunch of Boolean logic gates. Duplicating these wires does not increase the complexity of the circuit, because no additional logic gates are required.

Replacing the element at a _constant_ index in an array with a new value is equally cheap, because the Garble compiler can just duplicate the output wires of all the other elements and only has to use the wires of the replacement element where previously the old element was being used. In contrast, replacing the element at a _non-constant_ index (i.e. an index that depends on a runtime value) is a much more expensive operation in a Boolean circuit than it would be on a normal computer, because the Garble compiler has to generate a nested multiplexer circuit.

Expand Down
52 changes: 52 additions & 0 deletions garble_docs/src/serde.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# (De-)Serializing Garble Values

If the optional `"serde"` crate feature is enabled, Garble supports serializing literal values to/from formats supported via [serde](https://serde.rs/). The following [ABNF](https://en.wikipedia.org/wiki/Augmented_Backus%E2%80%93Naur_form) grammar shows how Garble literals are represented in JSON (using `serde_json`):

<pre class="abnf">
<code>literal = <span class="str">"\"True\""</span> /
<span class="str">"\"False\""</span> /
<span class="str">"{\"NumUnsigned\":["</span> uint <span class="str">","</span> uint-ty <span class="str">"]}"</span> /
<span class="str">"{\"NumSigned\":["</span> int <span class="str">","</span> int-ty <span class="str">"]}"</span> /
<span class="str">"{\"ArrayRepeat\":["</span> literal <span class="str">","</span> uint <span class="str">"]}"</span> /
<span class="str">"{\"Array\":["</span> [literal *(<span class="str">","</span> literal)] <span class="str">"]}"</span> /
<span class="str">"{\"Tuple\":["</span> [literal *(<span class="str">","</span> literal)] <span class="str">"]}"</span> /
<span class="str">"{\"Enum\":[\""</span> string <span class="str">"\",\""</span> string <span class="str">"\","</span> variant <span class="str">"]}"</span> /
<span class="str">"{\"Range\":["</span> uint <span class="str">","</span> uint <span class="str">","</span> uint-type <span class="str">"]}"</span>

uint = 1*DIGIT

uint-ty = <span class="str">"\"Usize\""</span> /
<span class="str">"\"U8\""</span> /
<span class="str">"\"U16\""</span> /
<span class="str">"\"U32\""</span> /
<span class="str">"\"U64\""</span> /
<span class="str">"\"Unspecified\""</span>

int = [<span class="str">"-"</span>] uint

int-ty = <span class="str">"\"I8\""</span> /
<span class="str">"\"I16\""</span> /
<span class="str">"\"I32\""</span> /
<span class="str">"\"I64\""</span> /
<span class="str">"\"Unspecified\""</span>

string = 1*ALPHA

variant = <span class="str">"\"Unit\""</span> /
<span class="str">"{\"Tuple\":["</span> [literal *(<span class="str">","</span> literal)] <span class="str">"]}"</span></code>
</pre>

Here are some example Garble literals and how they would be serialized as JSON:

| Garble Literal | Serialized as JSON |
| -------------------------------- | -------------------------------------------------------- |
| `true` | `"True"` |
| `200u32` | `{"NumUnsigned":[200,"U32"]}` |
| `-200` | `{"NumSigned":[-200,"Unspecified"]}` |
| `[true; 3]` | `{"ArrayRepeat":["True",3]}` |
| `[true, false]` | `{"Array":["True","False"]}` |
| `(true, false, 10)` | `{"Tuple":["True","False",{"NumUnsigned":[10,"U8"]}]}` |
| `FooBar {foo: true, bar: false}` | `{"Struct":["FooBar",[["foo","True"],["bar","False"]]]}` |
| `FooBar::Foo` | `{"Enum":["FooBar","Foo","Unit"]}` |
| `FooBar::Bar(true, false)` | `{"Enum":["FooBar","Bar",{"Tuple":["True","False"]}]}` |
| `2u8..10u8` | `{"Range":[2,10,"U8"]}` |
37 changes: 37 additions & 0 deletions garble_docs/theme/css/general.css
Original file line number Diff line number Diff line change
Expand Up @@ -359,4 +359,41 @@ a,

#sidebar a {
font-weight: normal;
}

.abnf,
.abnf>* {
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace !important;
line-height: 1.8;
}

.abnf .str {
display: inline;
padding: 0.1em 0.3em;
border-radius: 3px;
border: 1px solid #333;
background-color: #fff;
line-height: 1.8;
}

.table-wrapper {
overflow-x: auto;
}

table {
margin: 0;
border: none;
}

table tbody tr:nth-child(2n) {
background-color: unset !important;
}

table thead th {
text-align: left;
padding: 6px 6px;
}

table td {
padding: 6px 4px;
}
2 changes: 1 addition & 1 deletion src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ pub enum ExprEnum<T> {
/// Explicit cast of an expression to the specified type.
Cast(Type, Box<Expr<T>>),
/// Range of numbers from the specified min (inclusive) to the specified max (exclusive).
Range((u64, UnsignedNumType), (u64, UnsignedNumType)),
Range(u64, u64, UnsignedNumType),
}

/// The different kinds of variant literals.
Expand Down
18 changes: 3 additions & 15 deletions src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ pub enum TypeErrorEnum {
},
/// The two types are incompatible, (e.g. incompatible number types in a `+` operation).
TypeMismatch(Type, Type),
/// The specified range has different min and max types.
RangeTypeMismatch(UnsignedNumType, UnsignedNumType),
/// The specified range has invalid min or max values.
InvalidRange(u64, u64),
/// The specified pattern does not match the type of the matched expression.
Expand Down Expand Up @@ -201,9 +199,6 @@ impl std::fmt::Display for TypeErrorEnum {
TypeErrorEnum::TypeMismatch(x, y) => f.write_fmt(format_args!(
"The arguments have incompatible types; {x} vs {y}"
)),
TypeErrorEnum::RangeTypeMismatch(from, to) => f.write_fmt(format_args!(
"Start and end of range do not have the same type; {from} vs {to}"
)),
TypeErrorEnum::InvalidRange(_, _) => f.write_str("Invalid range"),
TypeErrorEnum::PatternDoesNotMatchType(ty) => {
f.write_fmt(format_args!("The pattern does not match the type {ty}"))
Expand Down Expand Up @@ -1156,20 +1151,13 @@ impl UntypedExpr {
expect_bool_or_num_type(&ty, meta)?;
(ExprEnum::Cast(ty.clone(), Box::new(expr)), ty)
}
ExprEnum::Range((from, from_suffix), (to, to_suffix)) => {
ExprEnum::Range(from, to, num_ty) => {
if from >= to || (to - from) > u32::MAX as u64 {
let e = TypeErrorEnum::InvalidRange(*from, *to);
return Err(vec![Some(TypeError(e, meta))]);
}
if from_suffix != to_suffix {
let e = TypeErrorEnum::RangeTypeMismatch(*from_suffix, *to_suffix);
return Err(vec![Some(TypeError(e, meta))]);
}
let ty = Type::Array(Box::new(Type::Unsigned(*from_suffix)), (to - from) as usize);
(
ExprEnum::Range((*from, *from_suffix), (*to, *to_suffix)),
ty,
)
let ty = Type::Array(Box::new(Type::Unsigned(*num_ty)), (to - from) as usize);
(ExprEnum::Range(*from, *to, *num_ty), ty)
}
ExprEnum::EnumLiteral(identifier, variant_name, variant) => {
if let Some(enum_def) = defs.enums.get(identifier.as_str()) {
Expand Down
4 changes: 2 additions & 2 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1016,10 +1016,10 @@ impl TypedExpr {
}
}
}
ExprEnum::Range((from, elem_ty), (to, _)) => {
ExprEnum::Range(from, to, num_ty) => {
let size = (to - from) as usize;
let elem_bits =
Type::Unsigned(*elem_ty).size_in_bits_for_defs(prg, circuit.const_sizes());
Type::Unsigned(*num_ty).size_in_bits_for_defs(prg, circuit.const_sizes());
let mut array = Vec::with_capacity(elem_bits * size);
for i in *from..*to {
for b in (0..elem_bits).rev() {
Expand Down
72 changes: 64 additions & 8 deletions src/literal.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! A subset of [`crate::ast::Expr`] that is used as input / output by an
//! [`crate::eval::Evaluator`].
//!
//! See [`crate::literal::Literal`] for examples on how to (de-)serialize to/from Garble literals
//! using `serde`.
use std::{
collections::{HashMap, HashSet},
Expand All @@ -23,6 +26,59 @@ use crate::{

/// A subset of [`crate::ast::Expr`] that is used as input / output by an
/// [`crate::eval::Evaluator`].
///
/// If the `serde` crate feature is enabled, literals can be (de-)serialized to any format supported
/// by `serde`. The following ABNF grammar shows how literals are represented when serialized using
/// `serde_json`:
///
/// ```asci
/// literal = "\"True\"" /
/// "\"False\"" /
/// "{\"NumUnsigned\":[" uint "," uint-ty "]}" /
/// "{\"NumSigned\":[" int "," int-ty "]}" /
/// "{\"ArrayRepeat\":[" literal "," uint "]}" /
/// "{\"Array\":[" [literal *("," literal)] "]}" /
/// "{\"Tuple\":[" [literal *("," literal)] "]}" /
/// "{\"Enum\":[\"" string "\",\"" string "\"," variant "]}" /
/// "{\"Range\":[" uint "," uint "," uint-type "]}"
///
/// uint = 1*DIGIT
///
/// uint-ty = "\"Usize\"" /
/// "\"U8\"" /
/// "\"U16\"" /
/// "\"U32\"" /
/// "\"U64\"" /
/// "\"Unspecified\""
///
/// int = ["-"] uint
///
/// int-ty = "\"I8\"" /
/// "\"I16\"" /
/// "\"I32\"" /
/// "\"I64\"" /
/// "\"Unspecified\""
///
/// string = 1*ALPHA
///
/// variant = "\"Unit\"" /
/// "{\"Tuple\":[" [literal *("," literal)] "]}"
/// ```
///
/// Here are some example Garble literals and how they would be serialized as JSON:
///
/// | Garble Literal | Serialized as JSON |
/// | -------------------------------- | -------------------------------------------------------- |
/// | `true` | `"True"` |
/// | `200u32` | `{"NumUnsigned":[200,"U32"]}` |
/// | `-200` | `{"NumSigned":[-200,"Unspecified"]}` |
/// | `[true; 3]` | `{"ArrayRepeat":["True",3]}` |
/// | `[true, false]` | `{"Array":["True","False"]}` |
/// | `(true, false, 10)` | `{"Tuple":["True","False",{"NumUnsigned":[10,"U8"]}]}` |
/// | `FooBar {foo: true, bar: false}` | `{"Struct":["FooBar",[["foo","True"],["bar","False"]]]}` |
/// | `FooBar::Foo` | `{"Enum":["FooBar","Foo","Unit"]}` |
/// | `FooBar::Bar(true, false)` | `{"Enum":["FooBar","Bar",{"Tuple":["True","False"]}]}` |
/// | `2u8..10u8` | `{"Range":[2,10,"U8"]}` |
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Literal {
Expand All @@ -45,7 +101,7 @@ pub enum Literal {
/// Enum literal of the specified variant, possibly with fields.
Enum(String, String, VariantLiteral),
/// Range of numbers from the specified min (inclusive) to the specified max (exclusive).
Range((u64, UnsignedNumType), (u64, UnsignedNumType)),
Range(u64, u64, UnsignedNumType),
}

/// A variant literal (either of unit type or containing fields), used by [`Literal::Enum`].
Expand Down Expand Up @@ -158,8 +214,8 @@ impl Literal {
}
false
}
(Literal::Range((min, min_ty), (max, _)), Type::Array(elem_ty, size)) => {
elem_ty.as_ref() == &Type::Unsigned(*min_ty) && max - min == *size as u64
(Literal::Range(min, max, num_ty), Type::Array(elem_ty, size)) => {
elem_ty.as_ref() == &Type::Unsigned(*num_ty) && max - min == *size as u64
}
_ => false,
}
Expand Down Expand Up @@ -423,9 +479,9 @@ impl Literal {
}
wires
}
Literal::Range((min, min_ty), (max, _)) => {
Literal::Range(min, max, num_ty) => {
let elems: Vec<usize> = (*min as usize..*max as usize).collect();
let elem_size = Type::Unsigned(*min_ty).size_in_bits_for_defs(checked, const_sizes);
let elem_size = Type::Unsigned(*num_ty).size_in_bits_for_defs(checked, const_sizes);
let mut bits = Vec::with_capacity(elems.len() * elem_size);
for elem in elems {
unsigned_to_bits(elem as u64, elem_size, &mut bits);
Expand Down Expand Up @@ -494,8 +550,8 @@ impl Display for Literal {
write!(f, ")")
}
},
Literal::Range((min, min_ty), (max, max_ty)) => {
write!(f, "{min}{min_ty}..{max}{max_ty}")
Literal::Range(min, max, num_ty) => {
write!(f, "{min}{num_ty}..{max}{num_ty}")
}
}
}
Expand Down Expand Up @@ -554,7 +610,7 @@ impl TypedExpr {
};
Literal::Enum(name, variant_name.clone(), variant)
}
ExprEnum::Range(min, max) => Literal::Range(min, max),
ExprEnum::Range(min, max, num_ty) => Literal::Range(min, max, num_ty),
_ => unreachable!("This should result in a literal parse error instead"),
}
}
Expand Down
Loading

0 comments on commit ba556d3

Please sign in to comment.