Skip to content

Commit

Permalink
Adds tuple type (#261)
Browse files Browse the repository at this point in the history
* support tuple type
  • Loading branch information
0xnullifier authored Jan 17, 2025
1 parent 2d76977 commit 1bcd765
Show file tree
Hide file tree
Showing 16 changed files with 591 additions and 127 deletions.
6 changes: 5 additions & 1 deletion examples/log.no
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ fn main(pub public_input: Field) -> Field {
log(arr);

let thing = Thing { xx : public_input , yy: public_input + 1};
log("formatted string with a number {} boolean {} arr {} struct {}" , 1234,true, arr , thing);

log(thing);

let tup = (1 , true , thing);
log("formatted string with a number {} boolean {} arr {} tuple {} struct {}" , 1234 , true, arr, tup, thing);

return public_input + 1;
}
47 changes: 47 additions & 0 deletions examples/tuple.no
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
struct Thing {
xx: Field,
tuple_field: (Field,Bool)
}

// return tuples from functions
fn Thing.new(xx: Field , tup: (Field,Bool)) -> (Thing , (Field,Bool)) {
return (
Thing {
xx: xx,
tuple_field:tup
},
tup
);
}

fn generic_array_tuple_test(var : ([[Field;NN];LEN],Bool)) -> (Field , [Field;NN]) {
let zero = 0;
let result = if var[1] {var[0][LEN - 1][NN - 1]} else { var[0][LEN - 2][NN - 2] };
return (result , var[0][LEN - 1]);
}

// xx should be 0
fn main(pub xx: [Field; 2]) -> Field {
// creation of new tuple with different types
let tup = (1, true);

// create nested tuples
let nested_tup = ((false, [1,2,3]), 1);
log(nested_tup); // (1, (true , [1,2,3]))

let incr = nested_tup[1]; // 1

// tuples can be input to function
let mut thing = Thing.new(xx[1] , (xx[0] , xx[0] == 0));

// you can access a tuple type just like you access a array
thing[0].tuple_field[0] += incr;
log(thing[0].tuple_field[0]);
let new_allocation = [xx,xx];
let ret = generic_array_tuple_test((new_allocation, true));

assert_eq(thing[0].tuple_field[0] , 1);
log(ret[1]); // logs xx i.e [0,123]

return ret[0];
}
34 changes: 31 additions & 3 deletions src/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
helpers::PrettyField,
imports::FnHandle,
parser::types::TyKind,
utils::{log_array_type, log_custom_type, log_string_type},
utils::{log_array_or_tuple_type, log_custom_type, log_string_type},
var::{ConstOrCell, Value, Var},
witness::WitnessEnv,
};
Expand Down Expand Up @@ -467,8 +467,20 @@ pub trait Backend: Clone {

// Array
Some(TyKind::Array(b, s)) => {
let (output, remaining) =
log_array_type(self, &var_info.var.cvars, b, *s, witness_env, typed, span)?;
let mut typs = Vec::with_capacity(*s as usize);
for _ in 0..(*s) {
typs.push((**b).clone());
}
let (output, remaining) = log_array_or_tuple_type(
self,
&var_info.var.cvars,
&typs,
*s,
witness_env,
typed,
span,
false,
)?;
assert!(remaining.is_empty());
println!("{dbg_msg}{}", output);
}
Expand Down Expand Up @@ -504,6 +516,22 @@ pub trait Backend: Clone {
println!("{dbg_msg}{}", output);
}

Some(TyKind::Tuple(typs)) => {
let len = typs.len();
let (output, remaining) = log_array_or_tuple_type(
self,
&var_info.var.cvars,
&typs,
len as u32,
witness_env,
typed,
span,
true,
)
.unwrap();
assert!(remaining.is_empty());
println!("{dbg_msg}{}", output);
}
None => {
return Err(Error::new(
"log",
Expand Down
50 changes: 38 additions & 12 deletions src/circuit_writer/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -972,11 +972,11 @@ impl<B: Backend> IRWriter<B> {
Ok(Some(res))
}

ExprKind::ArrayAccess { array, idx } => {
// retrieve var of array
ExprKind::ArrayOrTupleAccess { container, idx } => {
// retrieve var of container
let var = self
.compute_expr(fn_env, array)?
.expect("array access on non-array");
.compute_expr(fn_env, container)?
.expect("container access on non-container");

// compute the index
let idx_var = self
Expand All @@ -987,29 +987,41 @@ impl<B: Backend> IRWriter<B> {
.ok_or_else(|| self.error(ErrorKind::ExpectedConstant, expr.span))?;
let idx: usize = idx.try_into().unwrap();

// retrieve the type of the elements in the array
let array_typ = self.expr_type(array).expect("cannot find type of array");
// retrieve the type of the elements in the container
let container_typ = self
.expr_type(container)
.expect("cannot find type of container");

let elem_type = match array_typ {
// actual starting index for narrowing the var depends on the cotainer
// for arrays it is just idx * elem_size as all elements are of same size
// while for tuples we have to sum the sizes of all types up to that index
let (start, len) = match container_typ {
TyKind::Array(ty, array_len) => {
if idx >= (*array_len as usize) {
return Err(self.error(
ErrorKind::ArrayIndexOutOfBounds(idx, *array_len as usize - 1),
expr.span,
));
}
ty
let len = self.size_of(ty);
let start = idx * self.size_of(ty);
(start, len)
}

TyKind::Tuple(typs) => {
let mut starting_idx = 0;
for i in 0..idx {
starting_idx += self.size_of(&typs[i]);
}
(starting_idx, self.size_of(&typs[idx]))
}
_ => Err(Error::new(
"compute-expr",
ErrorKind::UnexpectedError("expected array"),
ErrorKind::UnexpectedError("expected container"),
expr.span,
))?,
};

// compute the size of each element in the array
let len = self.size_of(elem_type);

// compute the real index
let start = idx * len;

Expand Down Expand Up @@ -1074,6 +1086,20 @@ impl<B: Backend> IRWriter<B> {
let var = VarOrRef::Var(Var::new(cvars, expr.span));
Ok(Some(var))
}

ExprKind::TupleDeclaration(items) => {
let mut cvars = vec![];

for item in items {
let var = self.compute_expr(fn_env, item)?.unwrap();
let to_extend = var.value(self, fn_env).cvars.clone();
cvars.extend(to_extend);
}

let var = VarOrRef::Var(Var::new(cvars, expr.span));

Ok(Some(var))
}
}
}

Expand Down
63 changes: 48 additions & 15 deletions src/circuit_writer/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,15 @@ impl<B: Backend> CircuitWriter<B> {
unreachable!("generic array should have been resolved")
}
TyKind::String(_) => todo!("String type is not supported for constraints"),
TyKind::Tuple(types) => {
let mut offset = 0;
for ty in types {
let size = self.size_of(ty);
let slice = &input[offset..(offset + size)];
self.constrain_inputs_to_main(slice, input_typ, span)?;
offset += size;
}
}
};
Ok(())
}
Expand Down Expand Up @@ -726,11 +735,11 @@ impl<B: Backend> CircuitWriter<B> {
Ok(Some(res))
}

ExprKind::ArrayAccess { array, idx } => {
// retrieve var of array
ExprKind::ArrayOrTupleAccess { container, idx } => {
// retrieve var of container
let var = self
.compute_expr(fn_env, array)?
.expect("array access on non-array");
.compute_expr(fn_env, container)?
.expect("container access on non-container");

// compute the index
let idx_var = self
Expand All @@ -742,32 +751,41 @@ impl<B: Backend> CircuitWriter<B> {
let idx: BigUint = idx.into();
let idx: usize = idx.try_into().unwrap();

// retrieve the type of the elements in the array
let array_typ = self.expr_type(array).expect("cannot find type of array");
// retrieve the type of the elements in the container
let container_typ = self
.expr_type(container)
.expect("cannot find type of container");

let elem_type = match array_typ {
// actual starting index for narrowing the var depends on the cotainer
// for arrays it is just idx * elem_size as all elements are of same size
// while for tuples we have to sum the sizes of all types up to that index
let (start, len) = match container_typ {
TyKind::Array(ty, array_len) => {
if idx >= (*array_len as usize) {
return Err(self.error(
ErrorKind::ArrayIndexOutOfBounds(idx, *array_len as usize - 1),
expr.span,
));
}
ty
let len = self.size_of(ty);
let start = idx * self.size_of(ty);
(start, len)
}

TyKind::Tuple(typs) => {
let mut start = 0;
for i in 0..idx {
start += self.size_of(&typs[i]);
}
(start, self.size_of(&typs[idx]))
}
_ => Err(Error::new(
"compute-expr",
ErrorKind::UnexpectedError("expected array"),
ErrorKind::UnexpectedError("expected container"),
expr.span,
))?,
};

// compute the size of each element in the array
let len = self.size_of(elem_type);

// compute the real index
let start = idx * len;

// out-of-bound checks
if start >= var.len() || start + len > var.len() {
return Err(self.error(
Expand Down Expand Up @@ -830,6 +848,21 @@ impl<B: Backend> CircuitWriter<B> {
let var = VarOrRef::Var(Var::new(cvars, expr.span));
Ok(Some(var))
}
// exact copy of Array Declaration there is nothing really different at when looking it from a expression level
// as both of them are just `Vec<Expr>`
ExprKind::TupleDeclaration(items) => {
let mut cvars = vec![];

for item in items {
let var = self.compute_expr(fn_env, item)?.unwrap();
let to_extend = var.value(self, fn_env).cvars.clone();
cvars.extend(to_extend);
}

let var = VarOrRef::Var(Var::new(cvars, expr.span));

Ok(Some(var))
}
}
}

Expand Down
7 changes: 5 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ pub enum ErrorKind {
#[error("array accessed at index {0} is out of bounds (max allowed index is {1})")]
ArrayIndexOutOfBounds(usize, usize),

#[error("tuple accessed at index {0} is out of bounds (max allowed index is {1})")]
TupleIndexOutofBounds(usize, usize),

#[error(
"one-letter variables or types are not allowed. Best practice is to use descriptive names"
)]
Expand Down Expand Up @@ -327,8 +330,8 @@ pub enum ErrorKind {
#[error("field access can only be applied on custom structs")]
FieldAccessOnNonCustomStruct,

#[error("array access can only be performed on arrays")]
ArrayAccessOnNonArray,
#[error("array like access can only be performed on arrays or tuples")]
AccessOnNonCollection,

#[error("struct `{0}` does not exist (are you sure it is defined?)")]
UndefinedStruct(String),
Expand Down
16 changes: 16 additions & 0 deletions src/inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,22 @@ impl<B: Backend> CompiledCircuit<B> {

Ok(res)
}
// parsing for tuple function inputs from json
(TyKind::Tuple(types), Value::Array(values)) => {
if values.len() != types.len() {
Err(ParsingError::ArraySizeMismatch(
values.len(),
types.len() as usize,
))?
}
// making a vec with capacity allows for less number of reallocations
let mut res = Vec::with_capacity(values.len());
for (ty, val) in types.iter().zip(values) {
let el = self.parse_single_input(val, ty)?;
res.extend(el);
}
Ok(res)
}
(expected, observed) => {
return Err(ParsingError::MismatchJsonArgument(
expected.clone(),
Expand Down
Loading

0 comments on commit 1bcd765

Please sign in to comment.