From 4f5983b8a654f539de9bfe00af6a19b317e382be Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Thu, 18 Apr 2024 15:00:52 +0200 Subject: [PATCH 01/12] Fix inferred bound of list literals List bound is next power of two that is strictly greater than the number of elements. --- src/compile.rs | 2 +- src/lib.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compile.rs b/src/compile.rs index 889f5a8..875f56d 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -202,7 +202,7 @@ impl SingleExpression { let bound = if let Some(Type::List(_, bound)) = reqd_ty { *bound } else { - elements.len().next_power_of_two() + elements.len().saturating_add(1).next_power_of_two() }; debug_assert!(bound.is_power_of_two()); debug_assert!(2 <= bound); diff --git a/src/lib.rs b/src/lib.rs index 775d04f..9a4a5b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,6 @@ use crate::{ #[grammar = "minimal.pest"] pub struct IdentParser; - pub fn _compile(file: &Path) -> Arc>>> { let file = std::fs::read_to_string(file).unwrap(); let mut pairs = IdentParser::parse(Rule::program, &file).unwrap_or_else(|e| panic!("{}", e)); From d50ea74af288b7fa4143f0f315eae9deada174c6 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Fri, 19 Apr 2024 22:17:21 +0200 Subject: [PATCH 02/12] Fix boolean match statement There is a unit value that the scope needs to register in order to correctly extract variable values. --- src/compile.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/compile.rs b/src/compile.rs index 875f56d..21fd454 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -165,15 +165,24 @@ impl SingleExpression { right, } => { let mut l_scope = scope.clone(); - if let Some(x) = left.pattern.get_identifier() { - l_scope.insert(Pattern::Identifier(x.clone())); - } + l_scope.insert( + left.pattern + .get_identifier() + .cloned() + .map(Pattern::Identifier) + .unwrap_or(Pattern::Ignore), + ); let l_compiled = left.expression.eval(&mut l_scope, reqd_ty); let mut r_scope = scope.clone(); - if let Some(y) = right.pattern.get_identifier() { - r_scope.insert(Pattern::Identifier(y.clone())); - } + r_scope.insert( + right + .pattern + .get_identifier() + .cloned() + .map(Pattern::Identifier) + .unwrap_or(Pattern::Ignore), + ); let r_compiled = right.expression.eval(&mut r_scope, reqd_ty); // TODO: Enforce target type A + B for m_expr From 1a6a9e255f2e32a858255e768e5a594099e21c5a Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Fri, 19 Apr 2024 22:38:42 +0200 Subject: [PATCH 03/12] Test: Print compressed program For-while programs reach 20k+ nodes when not shared. This spams the command line, so only print shared programs. --- src/lib.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9a4a5b0..c8c80bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -178,22 +178,12 @@ mod tests { let prog = Program { statements: stmts }; let mut scope = GlobalScope::new(); let simplicity_prog = prog.eval(&mut scope); - let mut vec = Vec::new(); - let mut writer = BitWriter::new(&mut vec); - encode::encode_program(&simplicity_prog, &mut writer).unwrap(); - println!("{}", Base64Display::new(&vec, &STANDARD)); - dbg!(&simplicity_prog); let commit_node = simplicity_prog .finalize_types_main() .expect("Type check error"); // let commit_node = commit_node.to_commit_node(); let simplicity_prog = Arc::<_>::try_unwrap(commit_node).expect("Only one reference to commit node"); - dbg!(&simplicity_prog); - let mut vec = Vec::new(); - let mut writer = BitWriter::new(&mut vec); - let _encoded = encode::encode_program(&simplicity_prog, &mut writer).unwrap(); - println!("{}", Base64Display::new(&vec, &STANDARD)); struct MyConverter; @@ -244,6 +234,13 @@ mod tests { let redeem_prog = simplicity_prog .convert::, _>(&mut MyConverter) .unwrap(); + + let mut vec = Vec::new(); + let mut writer = BitWriter::new(&mut vec); + let _encoded = encode::encode_program(&redeem_prog, &mut writer).unwrap(); + dbg!(&redeem_prog); + println!("{}", Base64Display::new(&vec, &STANDARD)); + let mut bit_mac = BitMachine::for_program(&redeem_prog); let env = dummy_env::dummy(); bit_mac From b39ad292f03bcf89c3aea48149a3ab7872b89834 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Thu, 18 Apr 2024 00:12:59 +0200 Subject: [PATCH 04/12] Grammar: Make rules atomic Rules with constant values should be atomic to prevent unwanted whitespace. --- src/minimal.pest | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/minimal.pest b/src/minimal.pest index 2a9acca..baea973 100644 --- a/src/minimal.pest +++ b/src/minimal.pest @@ -15,7 +15,7 @@ witness_name = @{ (ASCII_ALPHANUMERIC | "_")+ } reserved = _{ jet | builtin } variable_pattern = { identifier } -ignore_pattern = { "_" } +ignore_pattern = @{ "_" } product_pattern = { "(" ~ pattern ~ "," ~ pattern ~ ")" } array_pattern = { "[" ~ pattern ~ ("," ~ pattern)* ~ ","? ~ "]" } pattern = { ignore_pattern | product_pattern | array_pattern | variable_pattern } @@ -23,42 +23,42 @@ assignment = { "let" ~ pattern ~ (":" ~ ty)? ~ "=" ~ expression } left_pattern = { "Left(" ~ identifier ~ ")" } right_pattern = { "Right(" ~ identifier ~ ")" } -none_pattern = { "None" } +none_pattern = @{ "None" } some_pattern = { "Some(" ~ identifier ~ ")" } -false_pattern = { "false" } -true_pattern = { "true" } +false_pattern = @{ "false" } +true_pattern = @{ "true" } match_pattern = { left_pattern | right_pattern | none_pattern | some_pattern | false_pattern | true_pattern } -unit_type = { "()" } +unit_type = @{ "()" } sum_type = { "Either<" ~ ty ~ "," ~ ty ~ ">" } product_type = { "(" ~ ty ~ "," ~ ty ~ ")" } option_type = { "Option<" ~ ty ~ ">" } -boolean_type = { "bool" } -unsigned_type = { "u128" | "u256" | "u16" | "u32" | "u64" | "u1" | "u2" | "u4" | "u8" } -array_size = { ASCII_DIGIT+ } +boolean_type = @{ "bool" } +unsigned_type = @{ "u128" | "u256" | "u16" | "u32" | "u64" | "u1" | "u2" | "u4" | "u8" } +array_size = @{ ASCII_DIGIT+ } array_type = { "[" ~ ty ~ ";" ~ array_size ~ "]" } -list_bound = { ASCII_DIGIT+ } +list_bound = @{ ASCII_DIGIT+ } list_type = { "List<" ~ ty ~ "," ~ list_bound ~ ">" } ty = { unit_type | sum_type | product_type | option_type | boolean_type | unsigned_type | array_type | list_type } expression = { block_expression | single_expression } block_expression = { "{" ~ (statement ~ ";")* ~ expression ~ "}" } -unit_expr = { "()" } +unit_expr = @{ "()" } left_expr = { "Left(" ~ expression ~ ")" } right_expr = { "Right(" ~ expression ~ ")" } product_expr = { "(" ~ expression ~ "," ~ expression ~ ")" } -none_expr = { "None" } +none_expr = @{ "None" } some_expr = { "Some(" ~ expression ~ ")" } -false_expr = { "false" } -true_expr = { "true" } +false_expr = @{ "false" } +true_expr = @{ "true" } jet_expr = { jet ~ "(" ~ (expression ~ ("," ~ expression)*)? ~ ")" } unwrap_left_expr = { "unwrap_left(" ~ expression ~ ")" } unwrap_right_expr = { "unwrap_right(" ~ expression ~ ")" } unwrap_expr = { "unwrap(" ~ expression ~ ")" } func_call = { jet_expr | unwrap_left_expr | unwrap_right_expr | unwrap_expr } -unsigned_integer = { ASCII_DIGIT+ } -bit_string = { "0b" ~ ASCII_BIN_DIGIT+ } -byte_string = { "0x" ~ ASCII_HEX_DIGIT+ } +unsigned_integer = @{ ASCII_DIGIT+ } +bit_string = @{ "0b" ~ ASCII_BIN_DIGIT+ } +byte_string = @{ "0x" ~ ASCII_HEX_DIGIT+ } witness_expr = { "witness(\"" ~ witness_name ~ "\")" } variable_expr = { identifier } match_arm = { match_pattern ~ "=>" ~ (single_expression ~ "," | block_expression ~ ","?) } From f5640d2c62000a4e7073c55ac5b23f25ea2b9c03 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Wed, 17 Apr 2024 16:27:51 +0200 Subject: [PATCH 05/12] Compile inner single expression This lets us construct single expressions in code and compile them to Simplicity. --- src/compile.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/compile.rs b/src/compile.rs index 21fd454..89787d2 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -8,9 +8,7 @@ use crate::array::{BTreeSlice, Partition}; use crate::parse::{Pattern, SingleExpressionInner, UIntType}; use crate::{ named::{ConstructExt, NamedConstructNode, ProgExt}, - parse::{ - Expression, ExpressionInner, FuncCall, FuncType, Program, SingleExpression, Statement, Type, - }, + parse::{Expression, ExpressionInner, FuncCall, FuncType, Program, Statement, Type}, scope::GlobalScope, ProgNode, }; @@ -107,14 +105,14 @@ impl Expression { scope.pop_scope(); res } - ExpressionInner::SingleExpression(e) => e.eval(scope, reqd_ty), + ExpressionInner::SingleExpression(e) => e.inner.eval(scope, reqd_ty), } } } -impl SingleExpression { +impl SingleExpressionInner { pub fn eval(&self, scope: &mut GlobalScope, reqd_ty: Option<&Type>) -> ProgNode { - let res = match &self.inner { + let res = match self { SingleExpressionInner::Unit => ProgNode::unit(), SingleExpressionInner::Left(l) => { let l = l.eval(scope, None); From 51e3f76d3988a51e1a5998cf0452a3e3d9016d08 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Wed, 17 Apr 2024 23:06:05 +0200 Subject: [PATCH 06/12] Switch Vec to Arc<[A]> Our data structures are almost always immutable. Using Arc<[A]> enables cheap cloning. --- src/parse.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/parse.rs b/src/parse.rs index d12cfff..d972402 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -39,7 +39,7 @@ pub enum Pattern { /// Split product value. Bind components to first and second pattern, respectively. Product(Arc, Arc), /// Split array value. Bind components to balanced binary tree of patterns. - Array(Vec), + Array(Arc<[Self]>), } impl Pattern { @@ -50,7 +50,11 @@ impl Pattern { /// Construct an array pattern. pub fn array>(array: I) -> Self { - Self::Array(array.into_iter().collect()) + let inner: Arc<_> = array.into_iter().collect(); + if inner.is_empty() { + panic!("Array must not be empty"); + } + Self::Array(inner) } /// Create an equivalent pattern that corresponds to the Simplicity base types. @@ -164,7 +168,7 @@ pub struct FuncCall { /// The type of the function. pub func_type: FuncType, /// The arguments to the function. - pub args: Vec, + pub args: Arc<[Expression]>, /// The source text associated with this expression pub source_text: Arc, /// The position of this expression in the source file. (row, col) @@ -261,11 +265,11 @@ pub enum SingleExpressionInner { right: MatchArm, }, /// Array wrapper expression - Array(Vec), + Array(Arc<[Expression]>), /// List wrapper expression /// /// The exclusive upper bound on the list size is not known at this point - List(Vec), + List(Arc<[Expression]>), } /// Bit string whose length is a power of two. @@ -278,7 +282,7 @@ pub enum Bits { /// Four least significant bits of byte U4(u8), /// All bits from byte string - Long(Vec), + Long(Arc<[u8]>), } impl Bits { @@ -295,7 +299,7 @@ impl Bits { /// Byte string whose length is a power of two. #[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct Bytes(pub Vec); +pub struct Bytes(pub Arc<[u8]>); impl Bytes { /// Convert the byte string into a Simplicity value. @@ -684,7 +688,7 @@ impl PestParse for Pattern { Rule::array_pattern => { assert!(0 < data.node.n_children(), "Array must be nonempty"); let children = output.split_off(output.len() - data.node.n_children()); - output.push(Pattern::Array(children)); + output.push(Pattern::Array(children.into_iter().collect())); } _ => unreachable!("Corrupt grammar"), } @@ -746,7 +750,7 @@ impl PestParse for FuncCall { FuncCall { func_type, - args, + args: args.into_iter().collect(), source_text, position, } @@ -875,7 +879,7 @@ impl PestParse for SingleExpression { } } Rule::array_expr => { - let elements: Vec<_> = inner_pair + let elements: Arc<_> = inner_pair .into_inner() .map(|inner| Expression::parse(inner)) .collect(); @@ -883,7 +887,7 @@ impl PestParse for SingleExpression { SingleExpressionInner::Array(elements) } Rule::list_expr => { - let elements: Vec<_> = inner_pair + let elements: Arc<_> = inner_pair .into_inner() .map(|inner| Expression::parse(inner)) .collect(); @@ -939,7 +943,7 @@ impl PestParse for Bits { debug_assert!(bytes[0] < 16); Bits::U4(bytes[0]) } - _ => Bits::Long(bytes), + _ => Bits::Long(bytes.into_iter().collect()), } } } @@ -956,7 +960,7 @@ impl PestParse for Bytes { } let bytes = Vec::::from_hex(hex_digits).unwrap(); - Bytes(bytes) + Bytes(bytes.into_iter().collect()) } } From b49cc420700ab82b2244f69c58b2fa3a698afe95 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Thu, 18 Apr 2024 14:51:39 +0200 Subject: [PATCH 07/12] Named: Add OIH shorthands Compiler enforces sequences of {O, I} followed by a final H. --- src/named.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/named.rs b/src/named.rs index 509e670..6d59dad 100644 --- a/src/named.rs +++ b/src/named.rs @@ -143,7 +143,7 @@ impl node::Marker for Named> { } } -pub trait ProgExt { +pub trait ProgExt: Sized { fn unit() -> Self; fn iden() -> Self; @@ -173,6 +173,53 @@ pub trait ProgExt { fn jet(jet: Elements) -> Self; fn const_word(v: Arc) -> Self; + + fn o() -> SelectorBuilder { + SelectorBuilder::default().o() + } + + fn i() -> SelectorBuilder { + SelectorBuilder::default().i() + } +} + +#[derive(Debug, Clone, Hash)] +pub struct SelectorBuilder

{ + selection: Vec, + program: PhantomData

, +} + +impl

Default for SelectorBuilder

{ + fn default() -> Self { + Self { + selection: Vec::default(), + program: PhantomData, + } + } +} + +impl SelectorBuilder

{ + pub fn o(mut self) -> Self { + self.selection.push(false); + self + } + + pub fn i(mut self) -> Self { + self.selection.push(true); + self + } + + pub fn h(self) -> P { + let mut ret = P::iden(); + for bit in self.selection.into_iter().rev() { + match bit { + false => ret = P::take(ret), + true => ret = P::drop_(ret), + } + } + + ret + } } impl ProgExt for ProgNode { From a519bd039323cc8b9caa1a3f1201ddf97e321280 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Fri, 19 Apr 2024 22:35:18 +0200 Subject: [PATCH 08/12] Add false | true shorthands --- src/named.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/named.rs b/src/named.rs index 6d59dad..3d4f875 100644 --- a/src/named.rs +++ b/src/named.rs @@ -181,6 +181,14 @@ pub trait ProgExt: Sized { fn i() -> SelectorBuilder { SelectorBuilder::default().i() } + + fn _false() -> Self { + Self::injl(Self::unit()) + } + + fn _true() -> Self { + Self::injr(Self::unit()) + } } #[derive(Debug, Clone, Hash)] From adc94089d601214fc2995e8a9687b574ba977593 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Thu, 18 Apr 2024 00:03:08 +0200 Subject: [PATCH 09/12] Add UnsignedDecimal Newtype for unsigned decimal strings. --- src/parse.rs | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/parse.rs b/src/parse.rs index d972402..b604b59 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -242,7 +242,7 @@ pub enum SingleExpressionInner { /// True literal expression True, /// Unsigned integer literal expression - UnsignedInteger(Arc), + UnsignedInteger(UnsignedDecimal), /// Bit string literal expression BitString(Bits), /// Byte string literal expression @@ -272,6 +272,23 @@ pub enum SingleExpressionInner { List(Arc<[Expression]>), } +/// Valid unsigned decimal string. +#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct UnsignedDecimal(Arc); + +impl UnsignedDecimal { + /// Access the inner decimal string. + pub fn as_inner(&self) -> &Arc { + &self.0 + } +} + +impl fmt::Display for UnsignedDecimal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + /// Bit string whose length is a power of two. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum Bits { @@ -587,15 +604,15 @@ impl UIntType { } /// Parse a decimal string for the type. - pub fn parse_decimal(&self, literal: &str) -> Arc { + pub fn parse_decimal(&self, decimal: &UnsignedDecimal) -> Arc { match self { - UIntType::U1 => Value::u1(literal.parse::().unwrap()), - UIntType::U2 => Value::u2(literal.parse::().unwrap()), - UIntType::U4 => Value::u4(literal.parse::().unwrap()), - UIntType::U8 => Value::u8(literal.parse::().unwrap()), - UIntType::U16 => Value::u16(literal.parse::().unwrap()), - UIntType::U32 => Value::u32(literal.parse::().unwrap()), - UIntType::U64 => Value::u64(literal.parse::().unwrap()), + UIntType::U1 => Value::u1(decimal.as_inner().parse::().unwrap()), + UIntType::U2 => Value::u2(decimal.as_inner().parse::().unwrap()), + UIntType::U4 => Value::u4(decimal.as_inner().parse::().unwrap()), + UIntType::U8 => Value::u8(decimal.as_inner().parse::().unwrap()), + UIntType::U16 => Value::u16(decimal.as_inner().parse::().unwrap()), + UIntType::U32 => Value::u32(decimal.as_inner().parse::().unwrap()), + UIntType::U64 => Value::u64(decimal.as_inner().parse::().unwrap()), UIntType::U128 => panic!("Use bit or hex strings for u128"), UIntType::U256 => panic!("Use bit or hex strings for u256"), } @@ -844,7 +861,9 @@ impl PestParse for SingleExpression { Rule::func_call => SingleExpressionInner::FuncCall(FuncCall::parse(inner_pair)), Rule::bit_string => SingleExpressionInner::BitString(Bits::parse(inner_pair)), Rule::byte_string => SingleExpressionInner::ByteString(Bytes::parse(inner_pair)), - Rule::unsigned_integer => SingleExpressionInner::UnsignedInteger(source_text.clone()), + Rule::unsigned_integer => { + SingleExpressionInner::UnsignedInteger(UnsignedDecimal::parse(inner_pair)) + } Rule::witness_expr => { let witness_pair = inner_pair.into_inner().next().unwrap(); SingleExpressionInner::Witness(WitnessName::parse(witness_pair)) @@ -904,6 +923,14 @@ impl PestParse for SingleExpression { } } +impl PestParse for UnsignedDecimal { + fn parse(pair: pest::iterators::Pair) -> Self { + assert!(matches!(pair.as_rule(), Rule::unsigned_integer)); + let decimal = Arc::from(pair.as_str()); + Self(decimal) + } +} + impl PestParse for Bits { fn parse(pair: pest::iterators::Pair) -> Self { assert!(matches!(pair.as_rule(), Rule::bit_string)); From 2d122bc07cd7a16b5b8063daba5e44caaf8491b4 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Fri, 19 Apr 2024 14:47:04 +0200 Subject: [PATCH 10/12] Use NonZeroUsize Guarantee at compile time that array sizes are nonzero. --- src/parse.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/parse.rs b/src/parse.rs index b604b59..30dd8c5 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::fmt; +use std::num::NonZeroUsize; use std::sync::Arc; use miniscript::iter::{Tree, TreeLike}; @@ -388,7 +389,7 @@ pub enum Type { Option(Arc), Boolean, UInt(UIntType), - Array(Arc, usize), + Array(Arc, NonZeroUsize), List(Arc, usize), } @@ -507,7 +508,7 @@ impl Type { Type::Array(_, size) => { let el = output.pop().unwrap(); // Cheap clone because SimType consists of Arcs - let el_vector = vec![el; *size]; + let el_vector = vec![el; size.get()]; let tree = BTreeSlice::from_slice(&el_vector); output.push(tree.fold(SimType::product)); } @@ -1041,7 +1042,7 @@ impl PestParse for Type { fn parse(pair: pest::iterators::Pair) -> Self { enum Item { Type(Type), - Size(usize), + Size(NonZeroUsize), } impl Item { @@ -1052,7 +1053,7 @@ impl PestParse for Type { } } - fn unwrap_size(self) -> usize { + fn unwrap_size(self) -> NonZeroUsize { match self { Item::Size(size) => size, _ => panic!("Not a size"), @@ -1095,20 +1096,21 @@ impl PestParse for Type { } Rule::array_size => { let size_str = data.node.0.as_str(); - let size = size_str.parse::().unwrap(); - assert!(0 < size, "Array size must be nonzero"); + let size = size_str + .parse::() + .expect("Array size must be nonzero"); output.push(Item::Size(size)); } Rule::list_type => { let bound = output.pop().unwrap().unwrap_size(); let el = output.pop().unwrap().unwrap_type(); - output.push(Item::Type(Type::List(Arc::new(el), bound))); + output.push(Item::Type(Type::List(Arc::new(el), bound.get()))); } Rule::list_bound => { let bound_str = data.node.0.as_str(); - let bound = bound_str.parse::().unwrap(); + let bound = bound_str.parse::().unwrap(); assert!(bound.is_power_of_two(), "List bound must be a power of two"); - assert!(2 <= bound, "List bound must be greater equal two"); + assert!(2 <= bound.get(), "List bound must be greater equal two"); output.push(Item::Size(bound)); } Rule::ty => {} From 85a8104b53cc405de2bb3ff9a62bf9d603f71e3b Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Sat, 20 Apr 2024 10:44:22 +0200 Subject: [PATCH 11/12] Add NonZeroPow2Usize List bounds must be powers of two with nonzero exponent. Enforce this requirement at compile time using a newtype. --- src/compile.rs | 7 ++--- src/lib.rs | 1 + src/num.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/parse.rs | 33 ++++++++++++-------- 4 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 src/num.rs diff --git a/src/compile.rs b/src/compile.rs index 89787d2..52ed87f 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -5,6 +5,7 @@ use std::{str::FromStr, sync::Arc}; use simplicity::{jet::Elements, node, Cmr, FailEntropy}; use crate::array::{BTreeSlice, Partition}; +use crate::num::NonZeroPow2Usize; use crate::parse::{Pattern, SingleExpressionInner, UIntType}; use crate::{ named::{ConstructExt, NamedConstructNode, ProgExt}, @@ -209,12 +210,10 @@ impl SingleExpressionInner { let bound = if let Some(Type::List(_, bound)) = reqd_ty { *bound } else { - elements.len().saturating_add(1).next_power_of_two() + NonZeroPow2Usize::next(elements.len().saturating_add(1)) }; - debug_assert!(bound.is_power_of_two()); - debug_assert!(2 <= bound); - let partition = Partition::from_slice(&nodes, bound / 2); + let partition = Partition::from_slice(&nodes, bound.get() / 2); let process = |block: &[ProgNode]| -> ProgNode { if block.is_empty() { ProgNode::injl(ProgNode::unit()) diff --git a/src/lib.rs b/src/lib.rs index c8c80bb..411fcac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ mod array; pub mod compile; pub mod dummy_env; pub mod named; +pub mod num; pub mod parse; pub mod scope; diff --git a/src/num.rs b/src/num.rs new file mode 100644 index 0000000..8495fb1 --- /dev/null +++ b/src/num.rs @@ -0,0 +1,82 @@ +/// Implementation for newtypes that wrap a number `u8`, `u16`, ... +/// such that the number has some property. +/// The newtype needs to have a constructor `Self::new(inner) -> Option`. +macro_rules! checked_num { + ( + $wrapper: ident, + $inner: ty, + $description: expr + ) => { + impl $wrapper { + /// Access the value as a primitive type. + pub const fn get(&self) -> usize { + self.0 + } + } + + impl std::fmt::Display for $wrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + + impl std::fmt::Debug for $wrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } + } + + impl std::str::FromStr for $wrapper { + type Err = String; + + fn from_str(s: &str) -> Result { + let n = s.parse::<$inner>().map_err(|e| e.to_string())?; + Self::new(n).ok_or(format!("{s} is not {}", $description)) + } + } + }; +} + +/// An integer that is known to be a power of two with nonzero exponent. +/// +/// The integer is equal to 2^n for some n > 0. +/// +/// The integer is strictly greater than 1. +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct NonZeroPow2Usize(usize); + +impl NonZeroPow2Usize { + /// Smallest power of two with nonzero exponent. + // FIXME `std::option::Option::::unwrap` is not yet stable as a const fn + // pub const TWO: Self = Self::new(2).unwrap(); + pub const TWO: Self = Self(2); + + /// Create a power of two with nonzero exponent. + pub const fn new(n: usize) -> Option { + if n.is_power_of_two() && 1 < n { + Some(Self(n)) + } else { + None + } + } + + /// Create the smallest power of two with nonzero exponent greater equal `n`. + pub const fn next(n: usize) -> Self { + if n < 2 { + Self::TWO + } else { + // FIXME `std::option::Option::::unwrap` is not yet stable as a const fn + // Self::new(n.next_power_of_two()).unwrap() + Self(n.next_power_of_two()) + } + } + + /// Return the binary logarithm of the value. + /// + /// The integer is equal to 2^n. Return n. + pub const fn log2(self) -> u32 { + self.0.trailing_zeros() + } +} + +checked_num!(NonZeroPow2Usize, usize, "a power of two greater than 1"); diff --git a/src/parse.rs b/src/parse.rs index 30dd8c5..9c914c1 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -12,6 +12,7 @@ use simplicity::types::Type as SimType; use simplicity::Value; use crate::array::{BTreeSlice, BinaryTree, Partition}; +use crate::num::NonZeroPow2Usize; use crate::Rule; /// A complete simplicity program. @@ -390,7 +391,7 @@ pub enum Type { Boolean, UInt(UIntType), Array(Arc, NonZeroUsize), - List(Arc, usize), + List(Arc, NonZeroPow2Usize), } /// Normalized unsigned integer type. @@ -467,9 +468,9 @@ impl Type { } integer_type.insert(data.node, uint); } - Type::List(el, bound) => match (el.as_ref(), bound) { + Type::List(el, bound) => match (el.as_ref(), *bound) { // List<1, 2> = Option<1> = u1 - (Type::Unit, 2) => { + (Type::Unit, NonZeroPow2Usize::TWO) => { integer_type.insert(data.node, UIntType::U1); } _ => return None, @@ -513,12 +514,10 @@ impl Type { output.push(tree.fold(SimType::product)); } Type::List(_, bound) => { - debug_assert!(bound.is_power_of_two()); - debug_assert!(2 <= *bound); let el = output.pop().unwrap(); // Cheap clone because SimType consists of Arcs - let el_vector = vec![el; *bound - 1]; - let partition = Partition::from_slice(&el_vector, *bound / 2); + let el_vector = vec![el; bound.get() - 1]; + let partition = Partition::from_slice(&el_vector, bound.get() / 2); debug_assert!(partition.is_complete()); let process = |block: &[SimType]| -> SimType { debug_assert!(!block.is_empty()); @@ -1043,6 +1042,7 @@ impl PestParse for Type { enum Item { Type(Type), Size(NonZeroUsize), + Bound(NonZeroPow2Usize), } impl Item { @@ -1059,6 +1059,13 @@ impl PestParse for Type { _ => panic!("Not a size"), } } + + fn unwrap_bound(self) -> NonZeroPow2Usize { + match self { + Item::Bound(size) => size, + _ => panic!("Not a bound"), + } + } } assert!(matches!(pair.as_rule(), Rule::ty)); @@ -1102,16 +1109,16 @@ impl PestParse for Type { output.push(Item::Size(size)); } Rule::list_type => { - let bound = output.pop().unwrap().unwrap_size(); + let bound = output.pop().unwrap().unwrap_bound(); let el = output.pop().unwrap().unwrap_type(); - output.push(Item::Type(Type::List(Arc::new(el), bound.get()))); + output.push(Item::Type(Type::List(Arc::new(el), bound))); } Rule::list_bound => { let bound_str = data.node.0.as_str(); - let bound = bound_str.parse::().unwrap(); - assert!(bound.is_power_of_two(), "List bound must be a power of two"); - assert!(2 <= bound.get(), "List bound must be greater equal two"); - output.push(Item::Size(bound)); + let bound = bound_str + .parse::() + .expect("List bound must be a power of two greater than 1"); + output.push(Item::Bound(bound)); } Rule::ty => {} _ => unreachable!("Corrupt grammar"), From a97ae560bed3c089c5508e2fa8a83db23286bdf5 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Sat, 20 Apr 2024 10:45:51 +0200 Subject: [PATCH 12/12] Add DoublePowwUsize The maximum number of iterations of a for-while loop is a power of a power of two. Enforce this requirement at compile time. --- src/num.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/num.rs b/src/num.rs index 8495fb1..5465a39 100644 --- a/src/num.rs +++ b/src/num.rs @@ -80,3 +80,29 @@ impl NonZeroPow2Usize { } checked_num!(NonZeroPow2Usize, usize, "a power of two greater than 1"); + +/// An integer that is known to be a power _of a power_ of two. +/// +/// The integer is equal to 2^(2^n) for some n ≥ 0. +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct DoublePow2Usize(usize); + +checked_num!(DoublePow2Usize, usize, "a double power of two"); + +impl DoublePow2Usize { + /// Create a double power of two. + pub const fn new(n: usize) -> Option { + if n.is_power_of_two() && n.trailing_zeros().is_power_of_two() { + Some(Self(n)) + } else { + None + } + } + + /// Return the binary logarithm _of the binary logarithm_ of the value. + /// + /// The integer is equal to 2^(2^n). Return n. + pub const fn log2_log2(self) -> u32 { + self.0.trailing_zeros().trailing_zeros() + } +}