Skip to content

Add beamish#864

Closed
jadnohra wants to merge 2 commits intomainfrom
08-29-beamish
Closed

Add beamish#864
jadnohra wants to merge 2 commits intomainfrom
08-29-beamish

Conversation

@jadnohra
Copy link
Copy Markdown
Contributor

No description provided.

@jadnohra jadnohra mentioned this pull request Aug 29, 2025
Copy link
Copy Markdown
Contributor Author

jadnohra commented Aug 29, 2025


How to use the Graphite Merge Queue

Add the label merge-ready to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@jadnohra jadnohra changed the title Rename to beamish Add beamish Aug 29, 2025
@jadnohra jadnohra changed the base branch from 08-28-spark2 to graphite-base/864 August 29, 2025 11:03
@jadnohra jadnohra changed the base branch from graphite-base/864 to main August 29, 2025 11:04
Comment on lines +67 to +70
if let Some(pattern_op) = op
&& pattern_op != expr_op {
return false;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The syntax in this if let statement is incorrect. Rust doesn't support combining if let with && in this manner. This should be rewritten as:

if let Some(pattern_op) = op {
    if pattern_op != expr_op {
        return false;
    }
}

This issue appears in multiple places in the pattern matching code.

Suggested change
if let Some(pattern_op) = op
&& pattern_op != expr_op {
return false;
}
if let Some(pattern_op) = op {
if pattern_op != expr_op {
return false;
}
}

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +65 to +79
|bindings| {
// This would be optimized to a single AND constraint
// For now, return a marked expression
Expr::Binary {
op: BinOp::Xor,
left: Box::new(bindings["a"].clone()),
right: Box::new(Expr::Binary {
op: BinOp::And,
left: Box::new(Expr::Unary {
op: UnOp::Not,
expr: Box::new(bindings["b"].clone()),
}),
right: Box::new(bindings["c"].clone()),
}),
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation returns the original expression structure rather than applying the optimization described in the design document. For true optimization, this should transform the expression into the optimized form: (b ^ 0xFFFFFFFFFFFFFFFF) & c ^ (a ^ result), which would compile to a single AND constraint instead of three.

Suggested change
|bindings| {
// This would be optimized to a single AND constraint
// For now, return a marked expression
Expr::Binary {
op: BinOp::Xor,
left: Box::new(bindings["a"].clone()),
right: Box::new(Expr::Binary {
op: BinOp::And,
left: Box::new(Expr::Unary {
op: UnOp::Not,
expr: Box::new(bindings["b"].clone()),
}),
right: Box::new(bindings["c"].clone()),
}),
}
|bindings| {
// Apply optimization to reduce to a single AND constraint
// Transform a ^ ((~b) & c) into (b ^ 0xFFFFFFFFFFFFFFFF) & c ^ (a ^ result)
let not_b = Expr::Binary {
op: BinOp::Xor,
left: Box::new(bindings["b"].clone()),
right: Box::new(Expr::Literal(Literal::Integer(u64::MAX))),
};
Expr::Binary {
op: BinOp::Xor,
left: Box::new(Expr::Binary {
op: BinOp::And,
left: Box::new(not_b),
right: Box::new(bindings["c"].clone()),
}),
right: Box::new(bindings["a"].clone()),
}

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@jadnohra jadnohra force-pushed the 08-29-beamish branch 6 times, most recently from 4ee621d to 9172d40 Compare August 29, 2025 13:38
}
}
Expr::Unary { op, expr: inner } => {
self.apply_rule_recursive(rule, inner).map(|new_inner| {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The apply_rule_recursive method has inconsistent behavior between expression types. For unary expressions, it always returns a new expression when a rule applies to the inner expression, but for binary expressions, it only returns a new expression when the inner expressions actually change. This inconsistency could lead to infinite recursion or unnecessary object creation.

Consider updating the unary case to match the binary pattern:

Expr::Unary { op, expr: inner } => {
    if let Some(new_inner) = self.apply_rule_recursive(rule, inner) {
        Some(Expr::Unary {
            op: *op,
            expr: Box::new(new_inner),
        })
    } else {
        None
    }
}

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@jadnohra jadnohra force-pushed the 08-29-beamish branch 4 times, most recently from 8d1ac0d to 8f94ef2 Compare August 29, 2025 15:13
Comment on lines +19 to +22
pub fn xor_many<T: BitType>(exprs: &[Expr<T>]) -> Expr<T> {
exprs.iter()
.skip(1)
.fold(exprs[0].clone(), |acc, e| xor(&acc, e))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The xor_many function will panic if called with an empty slice since it unconditionally accesses exprs[0] on line 20. Consider handling the empty case explicitly, either by:

pub fn xor_many<T: BitType>(exprs: &[Expr<T>]) -> Expr<T> {
    if exprs.is_empty() {
        // Return zero or panic with descriptive message
        return constant(0); // or panic!("Cannot XOR empty slice")
    }
    
    exprs.iter()
        .skip(1)
        .fold(exprs[0].clone(), |acc, e| xor(&acc, e))
}

This prevents unexpected runtime failures when the function is called with an empty collection.

Suggested change
pub fn xor_many<T: BitType>(exprs: &[Expr<T>]) -> Expr<T> {
exprs.iter()
.skip(1)
.fold(exprs[0].clone(), |acc, e| xor(&acc, e))
}
pub fn xor_many<T: BitType>(exprs: &[Expr<T>]) -> Expr<T> {
if exprs.is_empty() {
return constant(0);
}
exprs.iter()
.skip(1)
.fold(exprs[0].clone(), |acc, e| xor(&acc, e))
}

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +67 to +70
if let Some(pattern_op) = op
&& pattern_op != expr_op {
return false;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The syntax in this pattern matching logic is incorrect. The current code uses if let Some(pattern_op) = op && pattern_op != expr_op which combines a pattern match with a boolean condition in an invalid way. This should be rewritten as either:

if let Some(pattern_op) = op {
    if pattern_op != expr_op {
        return false;
    }
}

Or more concisely:

if let Some(pattern_op) = op && *pattern_op != *expr_op {
    return false;
}

This issue appears in multiple places in the pattern matching code and would cause compilation errors.

Suggested change
if let Some(pattern_op) = op
&& pattern_op != expr_op {
return false;
}
if let Some(pattern_op) = op {
if pattern_op != expr_op {
return false;
}
}

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment thread crates/beamish/examples/sha256.rs Outdated
//! SHA-256 implementation using Beamish expression system

use beamish::*;
use beamish::types::U32;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import statement in the SHA256 example uses beamish::*, but the crate name in Cargo.toml is defined as binius-beamish. This will cause compilation errors. The import should be changed to binius_beamish::* to match the actual crate name. This pattern appears in several example files and should be corrected throughout.

Suggested change
use beamish::types::U32;
use binius_beamish::types::U32;

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@jadnohra jadnohra force-pushed the 08-29-beamish branch 3 times, most recently from 1da1977 to a62c973 Compare August 29, 2025 16:02
Comment on lines +126 to +128
fn fmt_str(node: &ExprNode) -> String {
format!("{}", node)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fmt_str function calls format!("{}", node) which could lead to infinite recursion since Display for ExprNode calls fmt_node, which in turn may call fmt_str again. Consider using a direct approach to avoid this potential issue:

fn fmt_str(node: &ExprNode) -> String {
    let mut s = String::new();
    let mut f = fmt::Formatter::new(&mut s);
    fmt_node(node, &mut f).unwrap();
    s
}

Or alternatively, implement a separate formatting path specifically for this helper function.

Suggested change
fn fmt_str(node: &ExprNode) -> String {
format!("{}", node)
}
fn fmt_str(node: &ExprNode) -> String {
let mut s = String::new();
let mut f = fmt::Formatter::new(&mut s);
fmt_node(node, &mut f).unwrap();
s
}

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment thread crates/beamish/Cargo.toml
Comment on lines +10 to +11
log = "0.4"
env_logger = "0.11"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dependencies 'log' and 'env_logger' should refer to workspace-wide dependencies declared in the top-level Cargo.toml using the workspace syntax (e.g., log.workspace = true and env_logger.workspace = true) instead of specifying version numbers directly. This ensures consistent dependency versions across the workspace.

Spotted by Diamond (based on custom rule: Irreducible Rust and Cargo)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +1 to +109
//! Comprehensive tests for constraint generation

use binius_beamish::*;
use binius_beamish::types::{Field64, U32, U64};

#[test]
#[should_panic(expected = "Cannot generate constraints for expression")]
fn xor_chain_panics() {
let a = val::<Field64>(0);
let b = val::<Field64>(1);
let c = val::<Field64>(2);
let expr = xor4(&a, &b, &c, &val(3));
let _ = to_constraints(&expr);
}

#[test]
#[should_panic(expected = "Cannot generate constraints for expression")]
fn rotation_xor_panics() {
let v = val::<U32>(0);
let expr = xor3(&ror(&v, 7), &ror(&v, 18), &shr(&v, 3));
let _ = to_constraints(&expr);
}

#[test]
fn and_operation_succeeds() {
let a = val::<Field64>(0);
let b = val::<Field64>(1);
let expr = and(&a, &b);
let constraints = to_constraints(&expr);
assert_eq!(constraints.len(), 1);
}

#[test]
fn or_operation_succeeds() {
let a = val::<Field64>(0);
let b = val::<Field64>(1);
let expr = or(&a, &b);
let constraints = to_constraints(&expr);
assert_eq!(constraints.len(), 1);
}

#[test]
fn addition_succeeds() {
let x = val::<U32>(0);
let y = val::<U32>(1);
let expr = add(&x, &y);
let constraints = to_constraints(&expr);
assert_eq!(constraints.len(), 2); // Carry propagation + result
}

#[test]
fn multiplication_succeeds() {
let x = val::<U64>(0);
let y = val::<U64>(1);
let expr = mul64(&x, &y);
let constraints = to_constraints(&expr);
assert_eq!(constraints.len(), 1);
}

#[test]
fn keccak_chi_succeeds() {
let a = val::<Field64>(0);
let b = val::<Field64>(1);
let c = val::<Field64>(2);
let expr = keccak_chi(&a, &b, &c);
let constraints = to_constraints(&expr);
assert_eq!(constraints.len(), 1);
}

#[test]
fn multiplexer_succeeds() {
let a = val::<Field64>(0);
let b = val::<Field64>(1);
let c = val::<Field64>(2);
let expr = mux(&a, &b, &c);
let constraints = to_constraints(&expr);
assert_eq!(constraints.len(), 1);
}

#[test]
fn xor_with_equality_succeeds() {
let a = val::<Field64>(0);
let b = val::<Field64>(1);
let c = val::<Field64>(2);
let xor_expr = xor(&a, &b);
let eq_expr = eq(&c, &xor_expr);
let constraints = to_constraints(&eq_expr);
assert_eq!(constraints.len(), 1);
}

#[test]
fn not_with_equality_succeeds() {
let a = val::<Field64>(0);
let b = val::<Field64>(1);
let not_expr = not(&a);
let eq_expr = eq(&b, &not_expr);
let constraints = to_constraints(&eq_expr);
assert_eq!(constraints.len(), 1);
}

#[test]
fn rotation_with_equality_succeeds() {
let v = val::<U32>(0);
let w = val::<U32>(1);
let rot_expr = ror(&v, 7);
let eq_expr = eq(&w, &rot_expr);
let constraints = to_constraints(&eq_expr);
assert_eq!(constraints.len(), 1);
} No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test file does not use StdRng::seed_from_u64 for reproducible tests as required by the Pseudo-Random Testing rule. Tests should use seeded random number generation to ensure reproducibility. Additionally, the tests should consider using helper functions from binius_math::test_utils module where appropriate for better test utilities usage.

Spotted by Diamond (based on custom rule: Monbijou Testing Patterns)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@jadnohra jadnohra mentioned this pull request Sep 1, 2025
@jadnohra jadnohra closed this Sep 2, 2025
@jimpo jimpo deleted the 08-29-beamish branch November 21, 2025 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant