From f67c45774fab2070f08fdce140c42d2639e87af9 Mon Sep 17 00:00:00 2001 From: damirka Date: Sun, 27 Jun 2021 20:18:07 +0300 Subject: [PATCH 01/11] adds countdown loops rfc --- docs/rfc/005-countdown-loops.md | 98 +++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 docs/rfc/005-countdown-loops.md diff --git a/docs/rfc/005-countdown-loops.md b/docs/rfc/005-countdown-loops.md new file mode 100644 index 0000000000..3ec712b604 --- /dev/null +++ b/docs/rfc/005-countdown-loops.md @@ -0,0 +1,98 @@ +# Leo RFC 005: Countdown Loops + +## Authors + +- Max Bruce +- Collin Chin +- Alessandro Coglio +- Eric McCarthy +- Jon Pavlik +- Damir Shamanaev +- Damon Sicore +- Howard Wu + +## Status + +DRAFT + +# Summary + +This proposal suggests adding countdown loops and inclusive loop ranges into Leo language. + +# Motivation + +In the current design of the language only incremental ranges are allowed. Though +in some cases there's a need for loops going in reverse direction. This example +demonstrates bubble sort algorithm where count down loops are mocked: + +```ts +function bubble_sort(mut a: [u32; 10]) -> [u32; 10] { + for i in 0..9 { // i counts up + for j in 0..9-i { // i is flipped + if (a[j] > a[j+1]) { + let tmp = a[j]; + a[j] = a[j+1]; + a[j+1] = tmp; + } + } + } + return a +} +``` + +Having count down loop in the example above could improve readability and +usability of the language by making it more natural to the developer. + +However, if we imagined this example using countdown loop, we would see that +it wouldn't be possible to count to 0; because first bound of the range is +inclusive and second is exclusive, and loops ranges must use only unsigned integers. + +```ts +// loop goes 0,1,2,3,4,5,6,7,8 +for i in 0..9 { /* ... */ } + +// loop goes 9,8,7,6,5,4,3,2,1 +for i in 9..0 { /* ... */ } +``` + +Hence direct implementation of the coundown loop ranges would create asymmetry (1) +and would not allow loops to count down to 0 (2). To implement coundown loops and +solve these two problems we suggest adding inclusive range bounds. + +# Design + +## Coundown loops + +Countdown ranges do not need any changes to the existing syntax. However their +functionality needs to be implemented in the compiler. + +```ts +for i in 5..0 {} +``` + +## Inclusive ranges + +To solve loop asymmetry and to improve loop ranges in general we suggest adding +inclusive range operator to Leo. Inclusive range would extend second bound of the +loop making it inclusive (instead of default - exclusive) therefore allowing +countdown loops to reach 0 value. + +```ts +// default loop: 0,1,2,3,4 +for i in 0..5 {} + +// inclusive range: 0,1,2,3,4,5 +for i in 0..=5 {} +``` + +# Drawbacks + +- + +# Effect on Ecosystem + +Suggested change should have no effect on ecosystem because of its backward compatibility. + +# Alternatives + +Coundown loops can be mocked manually. From ad823ae8ebab2ab262ab8fb649ca7eff5e09a8d3 Mon Sep 17 00:00:00 2001 From: damirka Date: Mon, 28 Jun 2021 20:37:25 +0300 Subject: [PATCH 02/11] count down to countdown --- docs/rfc/005-countdown-loops.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rfc/005-countdown-loops.md b/docs/rfc/005-countdown-loops.md index 3ec712b604..aa9fae4c84 100644 --- a/docs/rfc/005-countdown-loops.md +++ b/docs/rfc/005-countdown-loops.md @@ -23,7 +23,7 @@ This proposal suggests adding countdown loops and inclusive loop ranges into Leo In the current design of the language only incremental ranges are allowed. Though in some cases there's a need for loops going in reverse direction. This example -demonstrates bubble sort algorithm where count down loops are mocked: +demonstrates bubble sort algorithm where countdown loops are mocked: ```ts function bubble_sort(mut a: [u32; 10]) -> [u32; 10] { @@ -40,7 +40,7 @@ function bubble_sort(mut a: [u32; 10]) -> [u32; 10] { } ``` -Having count down loop in the example above could improve readability and +Having countdown loop in the example above could improve readability and usability of the language by making it more natural to the developer. However, if we imagined this example using countdown loop, we would see that From f650e12bafd383b9c6e47c550b085654ef5c1c76 Mon Sep 17 00:00:00 2001 From: gluax Date: Fri, 2 Jul 2021 17:32:37 -0700 Subject: [PATCH 03/11] circuit cosnt_value --- asg-passes/src/constant_folding/mod.rs | 2 +- asg/src/const_value.rs | 17 ++++++++++------- asg/src/expression/array_access.rs | 2 +- asg/src/expression/array_init.rs | 2 +- asg/src/expression/array_inline.rs | 2 +- asg/src/expression/array_range_access.rs | 2 +- asg/src/expression/circuit_access.rs | 7 +++++-- asg/src/expression/circuit_init.rs | 8 ++++++-- asg/src/expression/constant.rs | 7 ++++--- asg/src/expression/mod.rs | 8 ++++---- asg/src/expression/ternary.rs | 2 +- asg/src/expression/tuple_access.rs | 2 +- asg/src/expression/tuple_init.rs | 2 +- asg/src/expression/variable_ref.rs | 2 +- compiler/src/expression/expression.rs | 15 +++++++++++++-- 15 files changed, 51 insertions(+), 29 deletions(-) diff --git a/asg-passes/src/constant_folding/mod.rs b/asg-passes/src/constant_folding/mod.rs index e01c9552c2..e18853336a 100644 --- a/asg-passes/src/constant_folding/mod.rs +++ b/asg-passes/src/constant_folding/mod.rs @@ -23,7 +23,7 @@ pub struct ConstantFolding<'a, 'b> { } impl<'a, 'b> ExpressionVisitor<'a> for ConstantFolding<'a, 'b> { - fn visit_expression(&mut self, input: &Cell<&Expression<'a>>) -> VisitResult { + fn visit_expression(&mut self, input: &Cell<&'a Expression<'a>>) -> VisitResult { let expr = input.get(); if let Some(const_value) = expr.const_value() { let folded_expr = Expression::Constant(Constant { diff --git a/asg/src/const_value.rs b/asg/src/const_value.rs index dc405faa4a..735e414516 100644 --- a/asg/src/const_value.rs +++ b/asg/src/const_value.rs @@ -14,8 +14,9 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{AsgConvertError, IntegerType, Span, Type}; +use crate::{AsgConvertError, Circuit, Identifier, IntegerType, Span, Type}; +use indexmap::IndexMap; use num_bigint::BigInt; use std::{convert::TryInto, fmt}; use tendril::StrTendril; @@ -118,8 +119,8 @@ impl From for CharValue { } } -#[derive(Clone, Debug, PartialEq)] -pub enum ConstValue { +#[derive(Clone, PartialEq)] +pub enum ConstValue<'a> { Int(ConstInt), Group(GroupValue), Field(BigInt), @@ -128,8 +129,9 @@ pub enum ConstValue { Char(CharValue), // compounds - Tuple(Vec), - Array(Vec), + Tuple(Vec>), + Array(Vec>), + Circuit(&'a Circuit<'a>, IndexMap>), } macro_rules! const_int_op { @@ -311,8 +313,8 @@ impl ConstInt { } } -impl ConstValue { - pub fn get_type<'a>(&self) -> Option> { +impl<'a> ConstValue<'a> { + pub fn get_type(&'a self) -> Option> { Some(match self { ConstValue::Int(i) => i.get_type(), ConstValue::Group(_) => Type::Group, @@ -324,6 +326,7 @@ impl ConstValue { Type::Tuple(sub_consts.iter().map(|x| x.get_type()).collect::>>()?) } ConstValue::Array(values) => Type::Array(Box::new(values.get(0)?.get_type()?), values.len()), + ConstValue::Circuit(circuit, _) => Type::Circuit(circuit), }) } diff --git a/asg/src/expression/array_access.rs b/asg/src/expression/array_access.rs index ad353ffaf6..2bbcf4f871 100644 --- a/asg/src/expression/array_access.rs +++ b/asg/src/expression/array_access.rs @@ -58,7 +58,7 @@ impl<'a> ExpressionNode<'a> for ArrayAccessExpression<'a> { self.array.get().is_mut_ref() } - fn const_value(&self) -> Option { + fn const_value(&self) -> Option> { let mut array = match self.array.get().const_value()? { ConstValue::Array(values) => values, _ => return None, diff --git a/asg/src/expression/array_init.rs b/asg/src/expression/array_init.rs index 09264dc60c..df2c2afe07 100644 --- a/asg/src/expression/array_init.rs +++ b/asg/src/expression/array_init.rs @@ -53,7 +53,7 @@ impl<'a> ExpressionNode<'a> for ArrayInitExpression<'a> { false } - fn const_value(&self) -> Option { + fn const_value(&self) -> Option> { let element = self.element.get().const_value()?; Some(ConstValue::Array(vec![element; self.len])) } diff --git a/asg/src/expression/array_inline.rs b/asg/src/expression/array_inline.rs index c1b700a763..6d53e0798f 100644 --- a/asg/src/expression/array_inline.rs +++ b/asg/src/expression/array_inline.rs @@ -78,7 +78,7 @@ impl<'a> ExpressionNode<'a> for ArrayInlineExpression<'a> { false } - fn const_value(&self) -> Option { + fn const_value(&self) -> Option> { let mut const_values = vec![]; for (expr, spread) in self.elements.iter() { if *spread { diff --git a/asg/src/expression/array_range_access.rs b/asg/src/expression/array_range_access.rs index aec0d79b2e..4a2ee6379b 100644 --- a/asg/src/expression/array_range_access.rs +++ b/asg/src/expression/array_range_access.rs @@ -70,7 +70,7 @@ impl<'a> ExpressionNode<'a> for ArrayRangeAccessExpression<'a> { self.array.get().is_mut_ref() } - fn const_value(&self) -> Option { + fn const_value(&self) -> Option> { let mut array = match self.array.get().const_value()? { ConstValue::Array(values) => values, _ => return None, diff --git a/asg/src/expression/circuit_access.rs b/asg/src/expression/circuit_access.rs index 6b45545860..abd696a876 100644 --- a/asg/src/expression/circuit_access.rs +++ b/asg/src/expression/circuit_access.rs @@ -83,8 +83,11 @@ impl<'a> ExpressionNode<'a> for CircuitAccessExpression<'a> { } } - fn const_value(&self) -> Option { - None + fn const_value(&self) -> Option> { + match self.target.get()?.const_value()? { + ConstValue::Circuit(_, members) => members.get(&self.member).cloned(), + _ => None, + } } fn is_consty(&self) -> bool { diff --git a/asg/src/expression/circuit_init.rs b/asg/src/expression/circuit_init.rs index 3eced2d008..3d7d47d309 100644 --- a/asg/src/expression/circuit_init.rs +++ b/asg/src/expression/circuit_init.rs @@ -70,8 +70,12 @@ impl<'a> ExpressionNode<'a> for CircuitInitExpression<'a> { true } - fn const_value(&self) -> Option { - None + fn const_value(&self) -> Option> { + let mut members = IndexMap::new(); + for (identifier, member) in self.values.iter() { + members.insert(identifier.clone(), member.get().const_value()?); + } + Some(ConstValue::Circuit(self.circuit.get(), members)) } fn is_consty(&self) -> bool { diff --git a/asg/src/expression/constant.rs b/asg/src/expression/constant.rs index 16f307b195..8fb638974a 100644 --- a/asg/src/expression/constant.rs +++ b/asg/src/expression/constant.rs @@ -36,7 +36,7 @@ use std::cell::Cell; pub struct Constant<'a> { pub parent: Cell>>, pub span: Option, - pub value: ConstValue, // should not be compound constants + pub value: ConstValue<'a>, // should not be compound constants } impl<'a> Node for Constant<'a> { @@ -56,7 +56,7 @@ impl<'a> ExpressionNode<'a> for Constant<'a> { fn enforce_parents(&self, _expr: &'a Expression<'a>) {} - fn get_type(&self) -> Option> { + fn get_type(&'a self) -> Option> { self.value.get_type() } @@ -64,7 +64,7 @@ impl<'a> ExpressionNode<'a> for Constant<'a> { false } - fn const_value(&self) -> Option { + fn const_value(&self) -> Option> { Some(self.value.clone()) } @@ -267,6 +267,7 @@ impl<'a> Into for &Constant<'a> { ), ConstValue::Tuple(_) => unimplemented!(), ConstValue::Array(_) => unimplemented!(), + ConstValue::Circuit(_, _) => unimplemented!(), } } } diff --git a/asg/src/expression/mod.rs b/asg/src/expression/mod.rs index bbd40e8298..4778e3b6a6 100644 --- a/asg/src/expression/mod.rs +++ b/asg/src/expression/mod.rs @@ -124,9 +124,9 @@ pub trait ExpressionNode<'a>: Node { fn get_parent(&self) -> Option<&'a Expression<'a>>; fn enforce_parents(&self, expr: &'a Expression<'a>); - fn get_type(&self) -> Option>; + fn get_type(&'a self) -> Option>; fn is_mut_ref(&self) -> bool; - fn const_value(&self) -> Option; // todo: memoize + fn const_value(&'a self) -> Option; // todo: memoize fn is_consty(&self) -> bool; } @@ -194,7 +194,7 @@ impl<'a> ExpressionNode<'a> for Expression<'a> { } } - fn get_type(&self) -> Option> { + fn get_type(&'a self) -> Option> { use Expression::*; match self { VariableRef(x) => x.get_type(), @@ -236,7 +236,7 @@ impl<'a> ExpressionNode<'a> for Expression<'a> { } } - fn const_value(&self) -> Option { + fn const_value(&'a self) -> Option> { use Expression::*; match self { VariableRef(x) => x.const_value(), diff --git a/asg/src/expression/ternary.rs b/asg/src/expression/ternary.rs index 93d1a7df0f..56564a5a81 100644 --- a/asg/src/expression/ternary.rs +++ b/asg/src/expression/ternary.rs @@ -56,7 +56,7 @@ impl<'a> ExpressionNode<'a> for TernaryExpression<'a> { self.if_true.get().is_mut_ref() && self.if_false.get().is_mut_ref() } - fn const_value(&self) -> Option { + fn const_value(&self) -> Option> { if let Some(ConstValue::Boolean(switch)) = self.condition.get().const_value() { if switch { self.if_true.get().const_value() diff --git a/asg/src/expression/tuple_access.rs b/asg/src/expression/tuple_access.rs index 6a9b2839e6..fc21c08def 100644 --- a/asg/src/expression/tuple_access.rs +++ b/asg/src/expression/tuple_access.rs @@ -56,7 +56,7 @@ impl<'a> ExpressionNode<'a> for TupleAccessExpression<'a> { self.tuple_ref.get().is_mut_ref() } - fn const_value(&self) -> Option { + fn const_value(&self) -> Option> { let tuple_const = self.tuple_ref.get().const_value()?; match tuple_const { ConstValue::Tuple(sub_consts) => sub_consts.get(self.index).cloned(), diff --git a/asg/src/expression/tuple_init.rs b/asg/src/expression/tuple_init.rs index 2dc40db252..90397989de 100644 --- a/asg/src/expression/tuple_init.rs +++ b/asg/src/expression/tuple_init.rs @@ -58,7 +58,7 @@ impl<'a> ExpressionNode<'a> for TupleInitExpression<'a> { false } - fn const_value(&self) -> Option { + fn const_value(&self) -> Option> { let mut consts = vec![]; for element in self.elements.iter() { if let Some(const_value) = element.get().const_value() { diff --git a/asg/src/expression/variable_ref.rs b/asg/src/expression/variable_ref.rs index 5e7ee39266..4a4e209223 100644 --- a/asg/src/expression/variable_ref.rs +++ b/asg/src/expression/variable_ref.rs @@ -66,7 +66,7 @@ impl<'a> ExpressionNode<'a> for VariableRef<'a> { } // todo: we can use use hacky ssa here to catch more cases, or just enforce ssa before asg generation finished - fn const_value(&self) -> Option { + fn const_value(&self) -> Option> { let variable = self.variable.borrow(); if variable.mutable || variable.assignments.len() != 1 { return None; diff --git a/compiler/src/expression/expression.rs b/compiler/src/expression/expression.rs index c72fb003a9..4da568646e 100644 --- a/compiler/src/expression/expression.rs +++ b/compiler/src/expression/expression.rs @@ -23,7 +23,7 @@ use crate::{ program::ConstrainedProgram, relational::*, resolve_core_circuit, - value::{Address, Char, CharType, ConstrainedValue, Integer}, + value::{Address, Char, CharType, ConstrainedCircuitMember, ConstrainedValue, Integer}, FieldType, GroupType, }; @@ -37,7 +37,7 @@ impl<'a, F: PrimeField, G: GroupType> ConstrainedProgram<'a, F, G> { pub(crate) fn enforce_const_value>( &mut self, cs: &mut CS, - value: &ConstValue, + value: &'a ConstValue<'a>, span: &Span, ) -> Result, ExpressionError> { Ok(match value { @@ -75,6 +75,17 @@ impl<'a, F: PrimeField, G: GroupType> ConstrainedProgram<'a, F, G> { .map(|x| self.enforce_const_value(cs, x, span)) .collect::, _>>()?, ), + ConstValue::Circuit(circuit, members) => { + let mut constrained_members = Vec::new(); + for (identifier, member) in members.iter() { + constrained_members.push(ConstrainedCircuitMember( + identifier.clone(), + self.enforce_const_value(cs, member, span)?, + )); + } + + ConstrainedValue::CircuitExpression(circuit, constrained_members) + } }) } From a496841563ab30d97137f8ae53ab1d99f159ae81 Mon Sep 17 00:00:00 2001 From: gluax Date: Fri, 2 Jul 2021 18:29:48 -0700 Subject: [PATCH 04/11] the identifiers differ, change it to name --- asg/src/const_value.rs | 2 +- asg/src/expression/array_range_access.rs | 1 + asg/src/expression/circuit_access.rs | 5 ++++- asg/src/expression/circuit_init.rs | 7 ++++++- compiler/src/expression/expression.rs | 2 +- tests/compiler/array/complex_access.leo | 7 ++++++- 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/asg/src/const_value.rs b/asg/src/const_value.rs index 735e414516..5a1c57d2a7 100644 --- a/asg/src/const_value.rs +++ b/asg/src/const_value.rs @@ -131,7 +131,7 @@ pub enum ConstValue<'a> { // compounds Tuple(Vec>), Array(Vec>), - Circuit(&'a Circuit<'a>, IndexMap>), + Circuit(&'a Circuit<'a>, IndexMap)>), } macro_rules! const_int_op { diff --git a/asg/src/expression/array_range_access.rs b/asg/src/expression/array_range_access.rs index 4a2ee6379b..773e8c66a9 100644 --- a/asg/src/expression/array_range_access.rs +++ b/asg/src/expression/array_range_access.rs @@ -176,6 +176,7 @@ impl<'a> FromAst<'a, leo_ast::ArrayRangeAccessExpression> for ArrayRangeAccessEx } else { None }; + if let Some(expected_len) = expected_len { if let Some(length) = length { if length != expected_len { diff --git a/asg/src/expression/circuit_access.rs b/asg/src/expression/circuit_access.rs index abd696a876..37af4e17bc 100644 --- a/asg/src/expression/circuit_access.rs +++ b/asg/src/expression/circuit_access.rs @@ -85,7 +85,10 @@ impl<'a> ExpressionNode<'a> for CircuitAccessExpression<'a> { fn const_value(&self) -> Option> { match self.target.get()?.const_value()? { - ConstValue::Circuit(_, members) => members.get(&self.member).cloned(), + ConstValue::Circuit(_, members) => { + let (_, const_value) = members.get(&self.member.name.to_string())?.clone(); + Some(const_value) + } _ => None, } } diff --git a/asg/src/expression/circuit_init.rs b/asg/src/expression/circuit_init.rs index 3d7d47d309..7c26aa25e3 100644 --- a/asg/src/expression/circuit_init.rs +++ b/asg/src/expression/circuit_init.rs @@ -73,8 +73,13 @@ impl<'a> ExpressionNode<'a> for CircuitInitExpression<'a> { fn const_value(&self) -> Option> { let mut members = IndexMap::new(); for (identifier, member) in self.values.iter() { - members.insert(identifier.clone(), member.get().const_value()?); + // insert by name because accessmembers identifiers are different. + members.insert( + identifier.name.to_string(), + (identifier.clone(), member.get().const_value()?), + ); } + // Store circuit as well for get_type. Some(ConstValue::Circuit(self.circuit.get(), members)) } diff --git a/compiler/src/expression/expression.rs b/compiler/src/expression/expression.rs index 4da568646e..cc55b6d1d7 100644 --- a/compiler/src/expression/expression.rs +++ b/compiler/src/expression/expression.rs @@ -77,7 +77,7 @@ impl<'a, F: PrimeField, G: GroupType> ConstrainedProgram<'a, F, G> { ), ConstValue::Circuit(circuit, members) => { let mut constrained_members = Vec::new(); - for (identifier, member) in members.iter() { + for (_, (identifier, member)) in members.iter() { constrained_members.push(ConstrainedCircuitMember( identifier.clone(), self.enforce_const_value(cs, member, span)?, diff --git a/tests/compiler/array/complex_access.leo b/tests/compiler/array/complex_access.leo index 4e68ad1c57..b6af9aa6e3 100644 --- a/tests/compiler/array/complex_access.leo +++ b/tests/compiler/array/complex_access.leo @@ -5,6 +5,10 @@ input_file: - input/complex_access.in */ +circuit Circ { + f: u32 +} + function main (a: [u8; 8], b: u32, c: [[u8; 3]; 3], d: [(u8, u32); 1], e: [u8; (3, 4)] ) -> bool { a[0..3][b] = 93; a[2..6][1] = 87; @@ -16,6 +20,7 @@ function main (a: [u8; 8], b: u32, c: [[u8; 3]; 3], d: [(u8, u32); 1], e: [u8; ( c[0..2][0] = [1u8; 3]; c[1..][1][1..2][0] = 126; c[1..][0] = [42, 43, 44]; + c[Circ {f: 0}.f..1][0][0] += 2; d[..1][0].1 = 1; @@ -24,7 +29,7 @@ function main (a: [u8; 8], b: u32, c: [[u8; 3]; 3], d: [(u8, u32); 1], e: [u8; ( return a == [200u8, 93, 42, 174, 5, 6, 43, 8] - && c == [[1u8, 1, 1], [42, 43, 44], [7, 126, 9]] + && c == [[3u8, 1, 1], [42, 43, 44], [7, 126, 9]] && d == [(0u8, 1u32)] && e == [[33u8, 22, 22, 22], [0, 0, 0, 0], [0, 0, 0, 0]]; } \ No newline at end of file From f4e270ffd2345f7b717b811cc924fc5610d8fa92 Mon Sep 17 00:00:00 2001 From: damirka Date: Mon, 5 Jul 2021 15:47:01 +0300 Subject: [PATCH 05/11] post review fixes --- docs/rfc/005-countdown-loops.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/rfc/005-countdown-loops.md b/docs/rfc/005-countdown-loops.md index aa9fae4c84..1baffc720b 100644 --- a/docs/rfc/005-countdown-loops.md +++ b/docs/rfc/005-countdown-loops.md @@ -22,8 +22,8 @@ This proposal suggests adding countdown loops and inclusive loop ranges into Leo # Motivation In the current design of the language only incremental ranges are allowed. Though -in some cases there's a need for loops going in reverse direction. This example -demonstrates bubble sort algorithm where countdown loops are mocked: +in some cases there's a need for loops going in the reverse direction. This example +demonstrates the bubble sort algorithm where countdown loops are mocked: ```ts function bubble_sort(mut a: [u32; 10]) -> [u32; 10] { @@ -40,12 +40,12 @@ function bubble_sort(mut a: [u32; 10]) -> [u32; 10] { } ``` -Having countdown loop in the example above could improve readability and +Having a countdown loop in the example above could improve readability and usability of the language by making it more natural to the developer. -However, if we imagined this example using countdown loop, we would see that -it wouldn't be possible to count to 0; because first bound of the range is -inclusive and second is exclusive, and loops ranges must use only unsigned integers. +However, if we imagined this example using a countdown loop, we would see that +it wouldn't be possible to count to 0; because the first bound of the range is +inclusive and the second is exclusive, and loops ranges must use only unsigned integers. ```ts // loop goes 0,1,2,3,4,5,6,7,8 @@ -57,7 +57,7 @@ for i in 9..0 { /* ... */ } Hence direct implementation of the coundown loop ranges would create asymmetry (1) and would not allow loops to count down to 0 (2). To implement coundown loops and -solve these two problems we suggest adding inclusive range bounds. +solve these two problems we suggest adding an inclusive range bounds. # Design @@ -73,9 +73,9 @@ for i in 5..0 {} ## Inclusive ranges To solve loop asymmetry and to improve loop ranges in general we suggest adding -inclusive range operator to Leo. Inclusive range would extend second bound of the -loop making it inclusive (instead of default - exclusive) therefore allowing -countdown loops to reach 0 value. +inclusive range operator to Leo. Inclusive range would extend the second bound +of the loop making it inclusive (instead of default - exclusive) +therefore allowing countdown loops to reach 0 value. ```ts // default loop: 0,1,2,3,4 From 5f840e3b1e6afb9128838b6cb63afe184418708d Mon Sep 17 00:00:00 2001 From: damirka Date: Mon, 5 Jul 2021 15:58:10 +0300 Subject: [PATCH 06/11] fix example - syntax outdated --- docs/rfc/005-countdown-loops.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rfc/005-countdown-loops.md b/docs/rfc/005-countdown-loops.md index 1baffc720b..a2d785a512 100644 --- a/docs/rfc/005-countdown-loops.md +++ b/docs/rfc/005-countdown-loops.md @@ -26,7 +26,7 @@ in some cases there's a need for loops going in the reverse direction. This exam demonstrates the bubble sort algorithm where countdown loops are mocked: ```ts -function bubble_sort(mut a: [u32; 10]) -> [u32; 10] { +function bubble_sort(a: [u32; 10]) -> [u32; 10] { for i in 0..9 { // i counts up for j in 0..9-i { // i is flipped if (a[j] > a[j+1]) { @@ -36,7 +36,7 @@ function bubble_sort(mut a: [u32; 10]) -> [u32; 10] { } } } - return a + return a; } ``` From d1f8e52eba295f4afe68f6b1b4d4e094fcc3b885 Mon Sep 17 00:00:00 2001 From: damirka Date: Mon, 5 Jul 2021 16:28:50 +0300 Subject: [PATCH 07/11] rework example to shaker sort --- docs/rfc/005-countdown-loops.md | 53 ++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/docs/rfc/005-countdown-loops.md b/docs/rfc/005-countdown-loops.md index a2d785a512..a1391a35d8 100644 --- a/docs/rfc/005-countdown-loops.md +++ b/docs/rfc/005-countdown-loops.md @@ -23,16 +23,25 @@ This proposal suggests adding countdown loops and inclusive loop ranges into Leo In the current design of the language only incremental ranges are allowed. Though in some cases there's a need for loops going in the reverse direction. This example -demonstrates the bubble sort algorithm where countdown loops are mocked: +demonstrates the shaker sort algorithm where countdown loops are mocked: ```ts -function bubble_sort(a: [u32; 10]) -> [u32; 10] { - for i in 0..9 { // i counts up - for j in 0..9-i { // i is flipped - if (a[j] > a[j+1]) { - let tmp = a[j]; - a[j] = a[j+1]; - a[j+1] = tmp; +function shaker_sort(a: [u32; 10], const rounds: u32) -> [u32; 10] { + for k in 0..rounds { + for i in 0..9 { + if a[i] > a[i + 1] { + let tmp = a[i]; + a[i] = a[i + 1]; + a[i + 1] = tmp; + } + + } + for j in 0..9 { // j goes from 0 to 8 + let i = 8 - j; // j is flipped + if a[i] > a[i + 1] { + let tmp = a[i]; + a[i] = a[i + 1]; + a[i + 1] = tmp; } } } @@ -85,6 +94,34 @@ for i in 0..5 {} for i in 0..=5 {} ``` +## Example + +The code example demostrated in the Motivation part of this document +could be extended (or simplified) with suggested syntax: + +```ts +function shaker_sort(a: [u32; 10], const rounds: u32) -> [u32; 10] { + for k in 0..rounds { + for i in 0..9 { // i goes from 0 to 8 + if a[i] > a[i + 1] { + let tmp = a[i]; + a[i] = a[i + 1]; + a[i + 1] = tmp; + } + + } + for i in 8..=0 { // j goes from 8 to 0 + if a[i] > a[i + 1] { + let tmp = a[i]; + a[i] = a[i + 1]; + a[i + 1] = tmp; + } + } + } + return a; +} +``` + # Drawbacks - From cc47350322d6286be9e8d8f1a20f182b6cb41b0d Mon Sep 17 00:00:00 2001 From: damirka Date: Mon, 5 Jul 2021 16:31:46 +0300 Subject: [PATCH 08/11] fix typo in comment --- docs/rfc/005-countdown-loops.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfc/005-countdown-loops.md b/docs/rfc/005-countdown-loops.md index a1391a35d8..1ee0799a04 100644 --- a/docs/rfc/005-countdown-loops.md +++ b/docs/rfc/005-countdown-loops.md @@ -110,7 +110,7 @@ function shaker_sort(a: [u32; 10], const rounds: u32) -> [u32; 10] { } } - for i in 8..=0 { // j goes from 8 to 0 + for i in 8..=0 { // i goes from 8 to 0 if a[i] > a[i + 1] { let tmp = a[i]; a[i] = a[i + 1]; From d27375d9d63dfb6c7dd31dd662f21b541479fa33 Mon Sep 17 00:00:00 2001 From: damirka Date: Mon, 5 Jul 2021 19:57:37 +0300 Subject: [PATCH 09/11] fix another typo --- docs/rfc/005-countdown-loops.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfc/005-countdown-loops.md b/docs/rfc/005-countdown-loops.md index 1ee0799a04..1d504ea45d 100644 --- a/docs/rfc/005-countdown-loops.md +++ b/docs/rfc/005-countdown-loops.md @@ -97,7 +97,7 @@ for i in 0..=5 {} ## Example The code example demostrated in the Motivation part of this document -could be extended (or simplified) with suggested syntax: +could be extended (or simplified) with the suggested syntax: ```ts function shaker_sort(a: [u32; 10], const rounds: u32) -> [u32; 10] { From 82a5527ea097eefe671deaced7c69288e3c560ba Mon Sep 17 00:00:00 2001 From: Alessandro Coglio Date: Tue, 6 Jul 2021 16:12:37 -0700 Subject: [PATCH 10/11] [RFC] Update the type casts RFC. Based on consensus within the Leo team, now the RFC prescribes the value-preserving semantics. The discussion on value-changing semantics has been moved to the 'Alternatives' section. Other text has been adapted and improved to reflect the chosen approach (before, value-preserving and value-changing were presented as two possible candidates for type casts). --- docs/rfc/004-integer-type-casts.md | 102 ++++++++++++++--------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/docs/rfc/004-integer-type-casts.md b/docs/rfc/004-integer-type-casts.md index 74f241fc74..946c34c73f 100644 --- a/docs/rfc/004-integer-type-casts.md +++ b/docs/rfc/004-integer-type-casts.md @@ -19,12 +19,9 @@ DRAFT This proposal provides support for casts among integer types in Leo. The syntax is similar to Rust. -Two possible semantics are discussed: -_value-preserving casts_, -which just serve to change types -but cause errors when values are not representable in the new types; -and _values-changing casts_, -which never cause errors but may change the mathematical values. +The semantics is _value-preserving_, +i.e. the casts just serve to change types +but cause errors when the mathematical values are not representable in the new types. # Motivation @@ -33,7 +30,7 @@ arithmetic integer operations require operands of the same type and return results of the same type. There are no implicit or explicit ways to turn, for example, a `u8` into a `u16`, even though -every non-negative integers that fits in 8 bits also fits in 16 bits. +every non-negative integer that fits in 8 bits also fits in 16 bits. However, the ability to convert values between different (integer) types is a useful feature that is normally found in programming languages. @@ -68,7 +65,7 @@ The proposed syntax is ``` where `` must have an integer type. -The ABNF grammar is modified as follows: +The ABNF grammar of Leo is modified as follows: ``` ; add this rule: cast-expression = unary-expression @@ -112,21 +109,33 @@ but the value in question is in the intersection of the two ranges. When the mathematical integer value of the expression is not representable in the type that the expression is cast to, -there are two possible approaches, discussed below. +there are two possible approaches: +_value-preserving casts_, +which just serve to change types +but cause errors when values are not representable in the new types; +and _values-changing casts_, +which never cause errors but may change the mathematical values. -### Value-Preserving Casts +Based on discussion and consensus within the Leo team, +this RFC proposes value-preserving casts; +value-changing casts are discussed in the 'Alternatives' section, +for completeness. -The first approach is to deem that situation erroneous. -That is, to require casts to always preserve the mathematical integer values. +With value-preserving casts, +when the mathematical integer value of the expression +is not representable in the type that the expression is cast to, +it is an error. +That is, we require casts to always preserve the mathematical integer values. +Recall that all inputs are known at compile time in Leo, +so these checks can be performed easily. -In this approach, casts only serve to change types, never values. +Thus integer casts only serve to change types, never values. When values are to be changed, separate (built-in) functions can be used, e.g. to mask bits and achieve the same effect as the value-changing casts discussed below. -From a point of view, this approach seems to match Leo's -treatment of potentially erroneous situations like integer overflows: -the principle is that developers should explicitly use +This approach Leo's treatment of potentially erroneous situations like integer overflows. +The principle is that developers should explicitly use operations that may overflow if that is their intention, rather than having those situation possibly occur unexpectedly. @@ -159,9 +168,32 @@ let r_low16: u32 = r & 0xFFFF; // assuming we have bitwise ops and hex literals let s: u16 = r_low16 as u16; // no value change here ``` -### Value-Changing Casts +## Compilation to R1CS -The second approach is the following: +It may be more efficient (in terms of number of R1CS constraints) +to compile Leo casts as if they had a value-changing semantics. +If the R1CS constraints represent Leo integers as bits, +the bits of the new value can be determined from the bits of the old value, +with additional zero or sign extension bits when needed +(see the details of the value-changing semantics in the 'Alternatives' section). +There is no need to add checks to the R1CS constraints +because the compiler ensures that the cast values do not actually change given the known inputs, +and therefore the value-changing and value-preserving semantics are equivalent on the known inputs. +The idea is that the R1CS constraints can have a "don't care" behavior on inputs that cause errors in Leo. + +# Drawbacks + +This proposal does not appear to bring any drawbacks, +other than making the language and compiler inevitably more complex. +But the benefits to support type casts justifies the extra complexity. + +# Effect on Ecosystem + +This proposal does not appear to have any direct effects on the ecosystem. + +# Alternatives + +As mentioned above, an alternative semantics for casts is value-changing: 1. `uN` to `uM` with `N < M`: just change type of value. 2. `uN` to `uM` with `N > M`: take low `M` bits of value. 3. `iN` to `iM` with `N < M`: just change type of value. @@ -185,7 +217,7 @@ considerations that apply to typical programming languages do not necessarily apply to Leo. Back to the somewhat abstract example in the section on value-preserving casts, -with value-changing casts, the expectation that the final result fits in `u16` +note that, with value-changing casts, the expectation that the final result fits in `u16` would have to be checked with explicit code: ``` ... // some computations on u32 values, which could not be done with u16 @@ -197,35 +229,3 @@ let s: u16 = r as u16; // could change value in principle, but does not here ``` However, it would be easy for a developer to neglect to add the checking code, and thus have the Leo code silently produce an unexpected result. - -## Compilation to R1CS - -It should be possible to compile Leo casts to the same R1CS constraints -whether we choose the value-preserving or value-changing semantics. -If the R1CS constraints represent Leo integers as bits, -the bits of the new value can be determined from the bits of the old value, -with additional zero or sign extension bits when needed. -This is clearly the value-changing behavior. -With the value-preserving behavior, -all casts for the known inputs are checked, -and thus we value-changing behavior coincides -with the value-preserving behavior if the checks succeed. -Thus, if he behavior of the R1CS constraints is "don't care" -for Leo program inputs that cause errors (such as cast errors), -the compilation strategy for value-changing casts -should be also adequate for value-preserving casts. - -# Drawbacks - -This proposal does not appear to bring any drawbacks, -other than making the language and compiler inevitably more complex. -But the benefits to support type casts justifies the extra complexity. - -# Effect on Ecosystem - -This proposal does not appear to have any direct effects on the ecosystem. - -# Alternatives - -The 'Design' section above already discusses two alternative semantics. -After we settle on one, the other one could be mentioned in this section. From dd016f46ad81f6e893db3638ca0a5df91ee23884 Mon Sep 17 00:00:00 2001 From: gluaxspeed Date: Thu, 8 Jul 2021 11:29:20 -0700 Subject: [PATCH 11/11] remove CARGO_NET_GIT_FETCH_WITH_CLI: true --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54871c0d37..8f0d7c2e18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,6 @@ jobs: - name: cargo fmt --check uses: actions-rs/cargo@v1 - env: - CARGO_NET_GIT_FETCH_WITH_CLI: true with: command: fmt args: --all -- --check @@ -120,7 +118,6 @@ jobs: command: test args: --all --features ci_skip env: - CARGO_NET_GIT_FETCH_WITH_CLI: true CARGO_INCREMENTAL: "0" - name: Install dependencies for code coverage