Skip to content
Open
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ target/
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

# IntelliJ project files
# IDE project files (for developers)
## IntelliJ
.idea
*.iml

## VSCode
.vscode

# mdbook HTML output dir
book/book/

Expand Down
35 changes: 35 additions & 0 deletions doc/architecture.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
# Architecture Notes

## The effect of imports and flattening on the CMR

Imports and the flattening phase have **no impact** on the CMR. The underlying order of calculation is strictly determined by the `main` function.

For example, consider the following code:

```rust
fn a() -> u32 { 5 }
fn b() -> u32 { 6 }

fn main() {
let a: u32 = a();
let b: u32 = b();
let (_, c): (bool, u32) = jet::add_32(a, b);
assert!(jet::eq_32(c, 11));
}
```

It does not matter whether `fn a()` or `fn b()` is declared first; the driver can reorder these declarations as it sees fit without affecting the CMR.

The flattening phase behaves in the exact same way. While it wraps the file's contents into a `mod unit_N { ... }` block, it does not alter the execution order inside `main`. The CMR will change if and only if we explicitly modify the execution order (e.g., swapping the evaluation of `let a` and `let b`) within the `main` function itself.

Below are three scenarios where resolution errors corrupt the CMR:

1. Incorrect Aliasing or Function Substitution
If the resolution phase maps an alias to the wrong function, the compiler silently substitutes one implementation for another. Since the program logic has changed, the resulting CMR will be entirely different.

2. Entry Point Fallback (main hijacking)
The CMR is rooted at the main function of the entry file. If the compiler does not enforce this, it may traverse the dependency graph and pick up a main from a dependency instead. This completely changes the execution graph.

3. Resolution Cache Poisoning (use path collisions)
When different package roots share structurally identical import paths (e.g., both a binary and a library declare `use crate::A::foo`), an improperly isolated resolution cache may link one context's import to the other's physical file. (See `functional_tests::identical_crate_uses_in_different_package_roots_do_not_poison_resolution_cache`.)

*Note: The scenarios listed above reflect bugs discovered during current testing. The list is ***not exhaustive*** and may be expanded as further testing uncovers additional edge cases.*

## Crate and Module Paths

The `crate` keyword is used to construct absolute paths where the path root is the current package's root directory. This provides an explicit and readable way to distinguish local imports from external library imports.
Expand Down
26 changes: 26 additions & 0 deletions examples/modules.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
mod math {
pub mod ops {
pub fn double(x: u32) -> u32 {
let (_, res): (bool, u32) = jet::add_32(x, x);
res
}
}
}

mod business_logic {
use crate::math::ops::double;

pub fn calculate_fee(base_price: u32, tax: u32) -> u32 {
let (_, res): (bool, u32) = jet::add_32(double(base_price), tax);
res
}
}

use crate::business_logic::calculate_fee;
fn main() {
let price: u32 = 15;
let tax: u32 = 5;

let total: u32 = calculate_fee(price, tax);
assert!(jet::eq_32(total, 35));
}
34 changes: 34 additions & 0 deletions examples/multiple_deps/flattened.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
mod unit_2 {
pub fn hash(x: u32, y: u32) -> u32 {
jet::xor_32(x, y)
}
}

mod unit_1 {
use crate::unit_2::hash as temp_hash;
pub fn get_root(tx1: u32, tx2: u32) -> u32 {
temp_hash(tx1, tx2)
}
pub fn hash(tx1: u32, tx2: u32) -> u32 {
jet::and_32(tx1, tx2)
}
}

// main unit
mod unit_0 {
use crate::unit_1::{get_root, hash as and_hash};
use crate::unit_2::hash as or_hash;

pub fn get_block_value_hash(prev_hash: u32, tx1: u32, tx2: u32) -> u32 {
let root: u32 = get_root(tx1, tx2);
or_hash(prev_hash, root)
}

fn main() {
let block_val_hash: u32 = get_block_value_hash(5, 10, 20);
assert!(jet::eq_32(block_val_hash, 27));
let first_value: u32 = 15;
let second_value: u32 = 22;
assert!(jet::eq_32(and_hash(first_value, second_value), 6));
}
}
24 changes: 24 additions & 0 deletions examples/simple_multidep/flattened.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
mod unit_1 {
pub fn add(a: u32, b: u32) -> u32 {
let (_, res): (bool, u32) = jet::add_32(a, b);
res
}
}

mod unit_2 {
pub fn sha256(data: u32) -> u256 {
let ctx: Ctx8 = jet::sha_256_ctx_8_init();
let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, data);
jet::sha_256_ctx_8_finalize(ctx)
}
}

mod unit_0 {
use crate::unit_1::add;
use crate::unit_2::sha256;

fn main() {
let sum: u32 = add(2, 3);
let hash: u256 = sha256(sum);
}
}
29 changes: 29 additions & 0 deletions examples/single_dep/flattened.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
mod unit_2 {
pub type Smth = u32;

pub fn get_five() -> u32 {
5
}
}

mod unit_1 {
pub use crate::unit_2::Smth;

pub fn two() -> Smth {
2
}
}

mod unit_0 {
pub use crate::unit_1::two as smth;
use crate::unit_2::{get_five, Smth};

fn seven() -> u32 {
7
}

fn main() {
let (_, temp): (bool, u32) = jet::add_32(smth(), get_five());
assert!(jet::eq_32(temp, seven()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub fn add() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
use lib::module::add;
mod unit_1 {}
fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub fn foo() -> u32 { 7 }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use crate::A::foo;

fn main() {
assert!(jet::eq_32(foo(), 7));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
mod unit_1 {
pub fn foo() -> u32 { 7 }
}

mod unit_3 {
pub fn foo() -> u32 { 8 }
}

mod unit_2 {
use crate::unit_3::foo;
pub fn bar() {}
}

mod unit_0 {
use crate::unit_1::foo;
use crate::unit_2::bar;
fn main() {
assert!(jet::eq_32(foo(), 7));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub fn foo() -> u32 { 8 }
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
use crate::A::foo;

pub fn bar() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use crate::A::foo;

use lib::B::bar;

fn main() {
assert!(jet::eq_32(foo(), 7));
}
17 changes: 3 additions & 14 deletions fuzz/fuzz_targets/compile_parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,17 @@
fn do_test(data: &[u8]) {
use arbitrary::Arbitrary;
use simplicityhl::ast::ElementsJetHinter;
use std::sync::Arc;

use simplicityhl::error::{ErrorCollector, WithContent};
use simplicityhl::{ast, driver, named, parse, ArbitraryOfType, Arguments};
use simplicityhl::error::WithContent;
use simplicityhl::{ast, named, parse, ArbitraryOfType, Arguments};

let mut u = arbitrary::Unstructured::new(data);
let parse_program = match parse::Program::arbitrary(&mut u) {
Ok(x) => x,
Err(_) => return,
};

let mut error_handler = ErrorCollector::new();
let driver_program = if let Some(program) =
driver::Program::from_parse(&parse_program, Arc::from(""), &mut error_handler)
{
program
} else {
return;
};

let ast_program =
match ast::Program::analyze(&driver_program, Box::new(ElementsJetHinter::new())) {
match ast::Program::analyze(&parse_program, Box::new(ElementsJetHinter::new())) {
Ok(x) => x,
Err(_) => return,
};
Expand Down
8 changes: 7 additions & 1 deletion fuzz/fuzz_targets/display_parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@ fn main() {}

#[cfg(fuzzing)]
libfuzzer_sys::fuzz_target!(|data: simplicityhl::parse::Program| {
do_test(data);
// TODO: Adapt to a multifile program (detailed in https://github.com/BlockstreamResearch/SimplicityHL/issues/350)
// Temporarily disabled to prevent panics during file_id initialization.

// do_test(data);

let _ = data;
});

#[cfg(test)]
mod test {

use simplicityhl::parse::{ParseFromStr, Program};
#[test]
#[ignore]
fn test() {
let program_test = r#"fn main() {
assert!(jet::eq_32(witness::A, witness::A));
Expand Down
Loading
Loading