Skip to content

Commit 31c1ee8

Browse files
committed
Merge #159: Fix array_fold "powers of 2" bug and turn on serde by default
f64ba1f array_fold: a few minor cleanups (Andrew Poelstra) 75b93b0 array_fold: add regression test for power-of-2 size tree array folding (Andrew Poelstra) 21f6ff8 array_fold: handle powers of two correctly (Andrew Poelstra) be22f90 turn on 'serde' feature by default (Andrew Poelstra) 17e4937 simc: add explicit error when trying to use a .wit file without serde (Andrew Poelstra) Pull request description: The serde thing is unrelated but it was super simple so I threw it in here. Can split it out if someone complains. The main part of this PR is the 4-line patch ```diff --- a/src/compile/builtins.rs +++ b/src/compile/builtins.rs @@ -21,6 +21,6 @@ f_powers_of_two: &Vec<ProgNode>, ) -> Result<ProgNode, simplicity::types::Error> { - if n == 1 { - return Ok(f_powers_of_two[0].clone()); + if n.is_power_of_two() { + return Ok(f_powers_of_two[n.ilog2() as usize].clone()); } // For n > 1 the next largest power is always >= 0 ``` Essentially, in `array_fold` we start by computing the folds for a bunch of power-of-2 array sizes, then use these as building blocks to do non-powers-of-2 in a sort of unbalanced tree algorithm. However, if our array size was exactly a power of 2 (or, I suspect, a multiple of any power of 2 except 1..), we would attempt to fold a power-of-2 tree with a 0-sized tree, which led to an assertion failure. In these cases we shouldn't be folding at all; we should be looking at our precomputed fold. I put this simple patch in one commit, and the following commit refactors the logic a bit to make this clearer. Fixes #144 Fixes #153 ACKs for top commit: canndrew: ACK f64ba1f Tree-SHA512: 6f37e26d2b3be2631d19d3d93b6e4912260669c4185ed40b17081e9a1d576564cca48397f21ffa76faa77cd40d8c4cad65a5af285e46322c8c3a47d3825db9a7
2 parents 6e30622 + f64ba1f commit 31c1ee8

File tree

5 files changed

+43
-11
lines changed

5 files changed

+43
-11
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ name = "simc"
1818
path = "src/main.rs"
1919

2020
[features]
21+
default = [ "serde" ]
2122
serde = ["dep:serde", "dep:serde_json"]
2223

2324
[dependencies]

examples/array_fold_2n.simf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// From https://github.com/BlockstreamResearch/SimplicityHL/issues/153
2+
3+
fn sum(elt: u32, acc: u32) -> u32 {
4+
let (_, acc): (bool, u32) = jet::add_32(elt, acc);
5+
acc
6+
}
7+
8+
fn main() {
9+
let arr: [u32; 8] = [1, 1, 1, 1, 1, 1, 1, 1];
10+
let sum: u32 = array_fold::<sum, 8>(arr, 0);
11+
assert!(jet::eq_32(sum, 8));
12+
}

src/compile/builtins.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::num::NonZeroUsize;
2+
use std::sync::Arc;
23

34
use simplicity::node::CoreConstructible;
45

@@ -18,16 +19,20 @@ pub fn array_fold(size: NonZeroUsize, f: &ProgNode) -> Result<ProgNode, simplici
1819
/// Recursively fold the array using the precomputed folding functions.
1920
fn tree_fold(
2021
n: usize,
21-
f_powers_of_two: &Vec<ProgNode>,
22+
f_powers_of_two: &[ProgNode],
2223
) -> Result<ProgNode, simplicity::types::Error> {
23-
if n == 1 {
24-
return Ok(f_powers_of_two[0].clone());
25-
}
26-
// For n > 1 the next largest power is always >= 0
24+
// Array is a left-balanced (right-associative) binary tree.
2725
let max_pow2 = n.ilog2() as usize;
26+
debug_assert!(max_pow2 < f_powers_of_two.len());
27+
let f_right = &f_powers_of_two[max_pow2];
28+
29+
// If the tree is balanced, return precomputed solution.
2830
let size_right = 1 << max_pow2;
29-
// Array is a left-balanced (right-associative) binary tree.
30-
let f_right = f_powers_of_two.get(max_pow2).expect("max_pow2 OOB");
31+
if n == size_right {
32+
return Ok(Arc::clone(f_right));
33+
}
34+
debug_assert!(size_right < n);
35+
3136
let f_left = tree_fold(n - size_right, f_powers_of_two)?;
3237
f_array_fold(&f_left, f_right)
3338
}
@@ -51,7 +56,7 @@ pub fn array_fold(size: NonZeroUsize, f: &ProgNode) -> Result<ProgNode, simplici
5156

5257
// Precompute the folding functions for arrays of size 2^i where i < n.
5358
let n = size.get();
54-
let mut f_powers_of_two: Vec<ProgNode> = Vec::with_capacity(n.ilog2() as usize);
59+
let mut f_powers_of_two: Vec<ProgNode> = Vec::with_capacity(1 + n.ilog2() as usize);
5560

5661
// An array of size 1 is just the element itself, so f_array_fold_1 is the same as the folding function.
5762
let mut f_prev = f.clone();
@@ -60,7 +65,7 @@ pub fn array_fold(size: NonZeroUsize, f: &ProgNode) -> Result<ProgNode, simplici
6065
let mut i = 1;
6166
while i < n {
6267
f_prev = f_array_fold(&f_prev, &f_prev)?;
63-
f_powers_of_two.push(f_prev.clone());
68+
f_powers_of_two.push(Arc::clone(&f_prev));
6469
i *= 2;
6570
}
6671

src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,13 @@ pub(crate) mod tests {
436436
.assert_run_success();
437437
}
438438

439+
#[test]
440+
fn regression_153() {
441+
TestCase::program_file("./examples/array_fold_2n.simf")
442+
.with_witness_values(WitnessValues::default())
443+
.assert_run_success();
444+
}
445+
439446
#[test]
440447
#[cfg(feature = "serde")]
441448
fn sighash_non_interactive_fee_bump() {

src/main.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ fn run() -> Result<(), String> {
3333
)
3434
};
3535

36-
#[cfg(feature = "serde")]
3736
let command = {
3837
command.arg(
3938
Arg::new("wit_file")
@@ -75,7 +74,15 @@ fn run() -> Result<(), String> {
7574
.transpose()?
7675
};
7776
#[cfg(not(feature = "serde"))]
78-
let witness_opt: Option<simplicityhl::WitnessValues> = None;
77+
let witness_opt: Option<simplicityhl::WitnessValues> = {
78+
if matches.contains_id("wit_file") {
79+
return Err(
80+
"Program was compiled without the 'serde' feature and cannot process .wit files."
81+
.into(),
82+
);
83+
}
84+
None
85+
};
7986

8087
if let Some(witness) = witness_opt {
8188
let satisfied = compiled.satisfy(witness)?;

0 commit comments

Comments
 (0)