diff --git a/compiler/noirc_evaluator/src/acir/mod.rs b/compiler/noirc_evaluator/src/acir/mod.rs index 63facac5a17..cfba6ccf3a6 100644 --- a/compiler/noirc_evaluator/src/acir/mod.rs +++ b/compiler/noirc_evaluator/src/acir/mod.rs @@ -31,6 +31,7 @@ use crate::brillig::{ Brillig, }; use crate::errors::{InternalError, InternalWarning, RuntimeError, SsaReport}; +use crate::ssa::ir::instruction::Hint; use crate::ssa::{ function_builder::data_bus::DataBus, ir::{ @@ -2131,6 +2132,15 @@ impl<'a> Context<'a> { result_ids: &[ValueId], ) -> Result, RuntimeError> { match intrinsic { + Intrinsic::Hint(Hint::BlackBox) => { + // Identity function; at the ACIR level this is a no-op, it only affects the SSA. + assert_eq!( + arguments.len(), + result_ids.len(), + "ICE: BlackBox input and output lengths should match." + ); + Ok(arguments.iter().map(|v| self.convert_value(*v, dfg)).collect()) + } Intrinsic::BlackBox(black_box) => { // Slices are represented as a tuple of (length, slice contents). // We must check the inputs to determine if there are slices diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 9c88c559b59..7b4cdb2b8ce 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -8,7 +8,7 @@ use crate::brillig::brillig_ir::{ BrilligBinaryOp, BrilligContext, ReservedRegisters, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, }; use crate::ssa::ir::dfg::CallStack; -use crate::ssa::ir::instruction::ConstrainError; +use crate::ssa::ir::instruction::{ConstrainError, Hint}; use crate::ssa::ir::{ basic_block::BasicBlockId, dfg::DataFlowGraph, @@ -552,6 +552,10 @@ impl<'block> BrilligBlock<'block> { false, ); } + Intrinsic::Hint(Hint::BlackBox) => { + let result_ids = dfg.instruction_results(instruction_id); + self.convert_ssa_identity_call(arguments, dfg, result_ids); + } Intrinsic::BlackBox(bb_func) => { // Slices are represented as a tuple of (length, slice contents). // We must check the inputs to determine if there are slices @@ -874,6 +878,30 @@ impl<'block> BrilligBlock<'block> { self.brillig_context.codegen_call(func_id, &argument_variables, &return_variables); } + /// Copy the input arguments to the results. + fn convert_ssa_identity_call( + &mut self, + arguments: &[ValueId], + dfg: &DataFlowGraph, + result_ids: &[ValueId], + ) { + let argument_variables = + vecmap(arguments, |argument_id| self.convert_ssa_value(*argument_id, dfg)); + + let return_variables = vecmap(result_ids, |result_id| { + self.variables.define_variable( + self.function_context, + self.brillig_context, + *result_id, + dfg, + ) + }); + + for (src, dst) in argument_variables.into_iter().zip(return_variables) { + self.brillig_context.mov_instruction(dst.extract_register(), src.extract_register()); + } + } + fn validate_array_index( &mut self, array_variable: BrilligVariable, diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index 426659949bf..365255d67a7 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -94,53 +94,15 @@ pub(crate) fn optimize_into_acir( ) -> Result { let ssa_gen_span = span!(Level::TRACE, "ssa_generation"); let ssa_gen_span_guard = ssa_gen_span.enter(); - - let mut ssa = SsaBuilder::new( + let builder = SsaBuilder::new( program, options.ssa_logging.clone(), options.force_brillig_output, options.print_codegen_timings, &options.emit_ssa, - )? - .run_pass(Ssa::defunctionalize, "Defunctionalization") - .run_pass(Ssa::remove_paired_rc, "Removing Paired rc_inc & rc_decs") - .run_pass(Ssa::separate_runtime, "Runtime Separation") - .run_pass(Ssa::resolve_is_unconstrained, "Resolving IsUnconstrained") - .run_pass(|ssa| ssa.inline_functions(options.inliner_aggressiveness), "Inlining (1st)") - // Run mem2reg with the CFG separated into blocks - .run_pass(Ssa::mem2reg, "Mem2Reg (1st)") - .run_pass(Ssa::simplify_cfg, "Simplifying (1st)") - .run_pass(Ssa::as_slice_optimization, "`as_slice` optimization") - .try_run_pass( - Ssa::evaluate_static_assert_and_assert_constant, - "`static_assert` and `assert_constant`", - )? - .run_pass(Ssa::loop_invariant_code_motion, "Loop Invariant Code Motion") - .try_run_pass( - |ssa| ssa.unroll_loops_iteratively(options.max_bytecode_increase_percent), - "Unrolling", - )? - .run_pass(Ssa::simplify_cfg, "Simplifying (2nd)") - .run_pass(Ssa::flatten_cfg, "Flattening") - .run_pass(Ssa::remove_bit_shifts, "After Removing Bit Shifts") - // Run mem2reg once more with the flattened CFG to catch any remaining loads/stores - .run_pass(Ssa::mem2reg, "Mem2Reg (2nd)") - // Run the inlining pass again to handle functions with `InlineType::NoPredicates`. - // Before flattening is run, we treat functions marked with the `InlineType::NoPredicates` as an entry point. - // This pass must come immediately following `mem2reg` as the succeeding passes - // may create an SSA which inlining fails to handle. - .run_pass( - |ssa| ssa.inline_functions_with_no_predicates(options.inliner_aggressiveness), - "Inlining (2nd)", - ) - .run_pass(Ssa::remove_if_else, "Remove IfElse") - .run_pass(Ssa::fold_constants, "Constant Folding") - .run_pass(Ssa::remove_enable_side_effects, "EnableSideEffectsIf removal") - .run_pass(Ssa::fold_constants_using_constraints, "Constraint Folding") - .run_pass(Ssa::dead_instruction_elimination, "Dead Instruction Elimination (1st)") - .run_pass(Ssa::simplify_cfg, "Simplifying:") - .run_pass(Ssa::array_set_optimization, "Array Set Optimizations") - .finish(); + )?; + + let mut ssa = optimize_all(builder, options)?; let ssa_level_warnings = if options.skip_underconstrained_check { vec![] @@ -173,9 +135,54 @@ pub(crate) fn optimize_into_acir( let artifacts = time("SSA to ACIR", options.print_codegen_timings, || { ssa.into_acir(&brillig, options.expression_width) })?; + Ok(ArtifactsAndWarnings(artifacts, ssa_level_warnings)) } +/// Run all SSA passes. +fn optimize_all(builder: SsaBuilder, options: &SsaEvaluatorOptions) -> Result { + Ok(builder + .run_pass(Ssa::defunctionalize, "Defunctionalization") + .run_pass(Ssa::remove_paired_rc, "Removing Paired rc_inc & rc_decs") + .run_pass(Ssa::separate_runtime, "Runtime Separation") + .run_pass(Ssa::resolve_is_unconstrained, "Resolving IsUnconstrained") + .run_pass(|ssa| ssa.inline_functions(options.inliner_aggressiveness), "Inlining (1st)") + // Run mem2reg with the CFG separated into blocks + .run_pass(Ssa::mem2reg, "Mem2Reg (1st)") + .run_pass(Ssa::simplify_cfg, "Simplifying (1st)") + .run_pass(Ssa::as_slice_optimization, "`as_slice` optimization") + .try_run_pass( + Ssa::evaluate_static_assert_and_assert_constant, + "`static_assert` and `assert_constant`", + )? + .run_pass(Ssa::loop_invariant_code_motion, "Loop Invariant Code Motion") + .try_run_pass( + |ssa| ssa.unroll_loops_iteratively(options.max_bytecode_increase_percent), + "Unrolling", + )? + .run_pass(Ssa::simplify_cfg, "Simplifying (2nd)") + .run_pass(Ssa::flatten_cfg, "Flattening") + .run_pass(Ssa::remove_bit_shifts, "After Removing Bit Shifts") + // Run mem2reg once more with the flattened CFG to catch any remaining loads/stores + .run_pass(Ssa::mem2reg, "Mem2Reg (2nd)") + // Run the inlining pass again to handle functions with `InlineType::NoPredicates`. + // Before flattening is run, we treat functions marked with the `InlineType::NoPredicates` as an entry point. + // This pass must come immediately following `mem2reg` as the succeeding passes + // may create an SSA which inlining fails to handle. + .run_pass( + |ssa| ssa.inline_functions_with_no_predicates(options.inliner_aggressiveness), + "Inlining (2nd)", + ) + .run_pass(Ssa::remove_if_else, "Remove IfElse") + .run_pass(Ssa::fold_constants, "Constant Folding") + .run_pass(Ssa::remove_enable_side_effects, "EnableSideEffectsIf removal") + .run_pass(Ssa::fold_constants_using_constraints, "Constraint Folding") + .run_pass(Ssa::dead_instruction_elimination, "Dead Instruction Elimination (1st)") + .run_pass(Ssa::simplify_cfg, "Simplifying:") + .run_pass(Ssa::array_set_optimization, "Array Set Optimizations") + .finish()) +} + // Helper to time SSA passes fn time(name: &str, print_timings: bool, f: impl FnOnce() -> T) -> T { let start_time = chrono::Utc::now().time(); diff --git a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs index 7a4e336c33e..a3421fce8e6 100644 --- a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs +++ b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs @@ -5,7 +5,7 @@ use crate::errors::{InternalBug, SsaReport}; use crate::ssa::ir::basic_block::BasicBlockId; use crate::ssa::ir::function::RuntimeType; use crate::ssa::ir::function::{Function, FunctionId}; -use crate::ssa::ir::instruction::{Instruction, InstructionId, Intrinsic}; +use crate::ssa::ir::instruction::{Hint, Instruction, InstructionId, Intrinsic}; use crate::ssa::ir::value::{Value, ValueId}; use crate::ssa::ssa_gen::Ssa; use im::HashMap; @@ -209,6 +209,7 @@ impl Context { | Intrinsic::AsField | Intrinsic::AsSlice | Intrinsic::BlackBox(..) + | Intrinsic::Hint(Hint::BlackBox) | Intrinsic::DerivePedersenGenerators | Intrinsic::FromField | Intrinsic::SliceInsert diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 76409f6a20a..ba212fdad96 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -64,6 +64,7 @@ pub(crate) enum Intrinsic { ToBits(Endian), ToRadix(Endian), BlackBox(BlackBoxFunc), + Hint(Hint), FromField, AsField, AsWitness, @@ -95,6 +96,7 @@ impl std::fmt::Display for Intrinsic { Intrinsic::ToRadix(Endian::Big) => write!(f, "to_be_radix"), Intrinsic::ToRadix(Endian::Little) => write!(f, "to_le_radix"), Intrinsic::BlackBox(function) => write!(f, "{function}"), + Intrinsic::Hint(Hint::BlackBox) => write!(f, "black_box"), Intrinsic::FromField => write!(f, "from_field"), Intrinsic::AsField => write!(f, "as_field"), Intrinsic::AsWitness => write!(f, "as_witness"), @@ -144,6 +146,9 @@ impl Intrinsic { | Intrinsic::DerivePedersenGenerators | Intrinsic::FieldLessThan => false, + // Treat the black_box hint as-if it could potentially have side effects. + Intrinsic::Hint(Hint::BlackBox) => true, + // Some black box functions have side-effects Intrinsic::BlackBox(func) => matches!( func, @@ -214,6 +219,7 @@ impl Intrinsic { "is_unconstrained" => Some(Intrinsic::IsUnconstrained), "derive_pedersen_generators" => Some(Intrinsic::DerivePedersenGenerators), "field_less_than" => Some(Intrinsic::FieldLessThan), + "black_box" => Some(Intrinsic::Hint(Hint::BlackBox)), "array_refcount" => Some(Intrinsic::ArrayRefCount), "slice_refcount" => Some(Intrinsic::SliceRefCount), @@ -229,6 +235,16 @@ pub(crate) enum Endian { Little, } +/// Compiler hints. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub(crate) enum Hint { + /// Hint to the compiler to treat the call as having potential side effects, + /// so that the value passed to it can survive SSA passes without being + /// simplified out completely. This facilitates testing and reproducing + /// runtime behavior with constants. + BlackBox, +} + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] /// Instructions are used to perform tasks. /// The instructions that the IR is able to specify are listed below. diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index a8db5e2ff94..40dd0bca41a 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -21,7 +21,7 @@ use crate::ssa::{ opt::flatten_cfg::value_merger::ValueMerger, }; -use super::{Binary, BinaryOp, Endian, Instruction, SimplifyResult}; +use super::{Binary, BinaryOp, Endian, Hint, Instruction, SimplifyResult}; mod blackbox; @@ -326,6 +326,7 @@ pub(super) fn simplify_call( SimplifyResult::None } } + Intrinsic::Hint(Hint::BlackBox) => SimplifyResult::None, Intrinsic::BlackBox(bb_func) => { simplify_black_box_func(bb_func, arguments, dfg, block, call_stack) } diff --git a/compiler/noirc_evaluator/src/ssa/opt/hint.rs b/compiler/noirc_evaluator/src/ssa/opt/hint.rs new file mode 100644 index 00000000000..147367261a8 --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/opt/hint.rs @@ -0,0 +1,104 @@ +#[cfg(test)] +mod tests { + use acvm::acir::circuit::ExpressionWidth; + + use crate::{ + errors::RuntimeError, + ssa::{ + opt::assert_normalized_ssa_equals, optimize_all, Ssa, SsaBuilder, SsaEvaluatorOptions, + SsaLogging, + }, + }; + + fn run_all_passes(ssa: Ssa) -> Result { + let options = &SsaEvaluatorOptions { + ssa_logging: SsaLogging::None, + enable_brillig_logging: false, + force_brillig_output: false, + print_codegen_timings: false, + expression_width: ExpressionWidth::default(), + emit_ssa: None, + skip_underconstrained_check: true, + inliner_aggressiveness: 0, + max_bytecode_increase_percent: None, + }; + + let builder = SsaBuilder { + ssa, + ssa_logging: options.ssa_logging.clone(), + print_codegen_timings: false, + }; + + optimize_all(builder, options) + } + + /// Test that the `std::hint::black_box` function prevents some of the optimizations. + #[test] + fn test_black_box_hint() { + // fn main(sum: u32) { + // // This version simplifies into a single `constraint 50 == sum` + // assert_eq(loop(5, 10), sum); + // // This should preserve additions because `k` is opaque, as if it came from an input. + // assert_eq(loop(5, std::hint::black_box(10)), sum); + // } + // fn loop(n: u32, k: u32) -> u32 { + // let mut sum = 0; + // for _ in 0..n { + // sum = sum + k; + // } + // sum + // } + + // Initial SSA: + let src = " + acir(inline) fn main f0 { + b0(v0: u32): + v4 = call f1(u32 5, u32 10) -> u32 + v5 = eq v4, v0 + constrain v4 == v0 + v7 = call black_box(u32 10) -> u32 + v9 = call f1(u32 5, v7) -> u32 + v10 = eq v9, v0 + constrain v9 == v0 + return + } + acir(inline) fn loop f1 { + b0(v0: u32, v1: u32): + v3 = allocate -> &mut u32 + store u32 0 at v3 + jmp b1(u32 0) + b1(v2: u32): + v5 = lt v2, v0 + jmpif v5 then: b3, else: b2 + b3(): + v7 = load v3 -> u32 + v8 = add v7, v1 + store v8 at v3 + v10 = add v2, u32 1 + jmp b1(v10) + b2(): + v6 = load v3 -> u32 + return v6 + } + "; + + // After Array Set Optimizations: + let expected = " + acir(inline) fn main f0 { + b0(v0: u32): + constrain u32 50 == v0 + v4 = call black_box(u32 10) -> u32 + v5 = add v4, v4 + v6 = add v5, v4 + v7 = add v6, v4 + v8 = add v7, v4 + constrain v8 == u32 50 + return + } + "; + + let ssa = Ssa::from_str(src).unwrap(); + let ssa = run_all_passes(ssa).unwrap(); + assert_normalized_ssa_equals(ssa, expected); + } +} diff --git a/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/compiler/noirc_evaluator/src/ssa/opt/mod.rs index 06481a12f60..bd0c86570e2 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -11,6 +11,7 @@ mod constant_folding; mod defunctionalize; mod die; pub(crate) mod flatten_cfg; +mod hint; mod inlining; mod loop_invariant; mod mem2reg; diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index f735d9300ce..ce5534ecc7a 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -17,7 +17,7 @@ use crate::ssa::{ basic_block::BasicBlockId, dfg::DataFlowGraph, function::{Function, RuntimeType}, - instruction::{BinaryOp, Instruction, Intrinsic}, + instruction::{BinaryOp, Hint, Instruction, Intrinsic}, types::Type, value::Value, }, @@ -174,6 +174,7 @@ impl Context { | Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) | Intrinsic::BlackBox(_) + | Intrinsic::Hint(Hint::BlackBox) | Intrinsic::FromField | Intrinsic::AsField | Intrinsic::AsSlice diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index 02191801fcd..f93f63c1fbb 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -4,6 +4,7 @@ use acvm::{acir::AcirField, FieldElement}; use fxhash::FxHashMap as HashMap; use crate::ssa::ir::function::RuntimeType; +use crate::ssa::ir::instruction::Hint; use crate::ssa::ir::value::ValueId; use crate::ssa::{ ir::{ @@ -231,6 +232,7 @@ fn slice_capacity_change( | Intrinsic::ArrayAsStrUnchecked | Intrinsic::StrAsBytes | Intrinsic::BlackBox(_) + | Intrinsic::Hint(Hint::BlackBox) | Intrinsic::FromField | Intrinsic::AsField | Intrinsic::AsWitness diff --git a/noir_stdlib/src/hint.nr b/noir_stdlib/src/hint.nr new file mode 100644 index 00000000000..25dcc7ec56e --- /dev/null +++ b/noir_stdlib/src/hint.nr @@ -0,0 +1,6 @@ +/// An identity function that *hints* to the compiler to be maximally pessimistic about what `black_box` could do. +/// +/// This can be used to block the SSA optimization passes being applied to a value, which should help to prevent +/// test programs from being optimized down to nothing and have them resemble runtime code more closely. +#[builtin(black_box)] +pub fn black_box(value: T) -> T {} diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 8e9dc13c13d..1cce9f2c330 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -27,6 +27,7 @@ pub mod meta; pub mod append; pub mod mem; pub mod panic; +pub mod hint; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident @@ -115,4 +116,3 @@ pub fn wrapping_mul(x: T, y: T) -> T { #[builtin(as_witness)] pub fn as_witness(x: Field) {} - diff --git a/test_programs/execution_success/hint_black_box/Nargo.toml b/test_programs/execution_success/hint_black_box/Nargo.toml new file mode 100644 index 00000000000..8a49ec25494 --- /dev/null +++ b/test_programs/execution_success/hint_black_box/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "hint_black_box" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/execution_success/hint_black_box/Prover.toml b/test_programs/execution_success/hint_black_box/Prover.toml new file mode 100644 index 00000000000..67dda9c2b68 --- /dev/null +++ b/test_programs/execution_success/hint_black_box/Prover.toml @@ -0,0 +1,3 @@ +# 5 * a = b +a = 10 +b = 50 diff --git a/test_programs/execution_success/hint_black_box/src/main.nr b/test_programs/execution_success/hint_black_box/src/main.nr new file mode 100644 index 00000000000..1109c54301f --- /dev/null +++ b/test_programs/execution_success/hint_black_box/src/main.nr @@ -0,0 +1,90 @@ +use std::hint::black_box; + +fn main(a: u32, b: u32) { + // This version unrolls into a number of additions + assert_eq(loop(5, a), b); + // This version simplifies into a single `constraint 50 == b` + assert_eq(loop(5, 10), b); + // This version should not simplify down to a single constraint, + // it should treat 10 as opaque: + assert_eq(loop(5, black_box(10)), b); + + // Check array handling. + let arr = [a, a, a, a, a]; + + assert_eq(array_sum(arr), b); + assert_eq(array_sum(black_box(arr)), b); + + assert_eq(slice_sum(arr.as_slice()), b); + assert_eq(slice_sum(black_box(arr).as_slice()), b); + + // This doesn't work because by calling `black_box` on a slice the compiler + // loses track of the length, and then cannot unroll the loop for ACIR. + //assert_eq(slice_sum(black_box(arr.as_slice())), b); + + // But we can pass a blackboxed slice to Brillig. + let s = unsafe { brillig_slice_sum(black_box(arr.as_slice())) }; + assert_eq(s, b); + + let mut d = b; + // This gets completely eliminated: + let mut c = 0; + set_ref(&mut c, &mut d); + assert_eq(c, b); + + // This way the constraint is preserved: + let mut c = 0; + set_ref(&mut c, &mut black_box(d)); + assert_eq(c, b); + + // A reference over the output of black box is not the original variable: + let mut c = 0; + set_ref(&mut black_box(c), &mut d); + assert_eq(c, 0); + + // This would cause a causes a crash during SSA passes unless it's a Brillig runtime: + // > Could not resolve some references to the array. All references must be resolved at compile time + // The SSA cannot have Allocate by the time we start generating ACIR, but `black_box` prevents them + // from being optimised away during SSA passes. + // If we use `--force-brillig` then the it doesn't crash but the assertion fails because `mem2reg` + // eliminates the storing to the reference. + //let mut c = 0; + //set_ref(black_box(&mut c), black_box(&mut d)); + //assert_eq(c, b); +} + +fn loop(n: u32, k: u32) -> u32 { + let mut sum = 0; + for _ in 0..n { + sum = sum + k; + } + sum +} + +fn array_sum(xs: [u32; N]) -> u32 { + let mut sum = 0; + for i in 0..N { + sum = sum + xs[i]; + } + sum +} + +fn slice_sum(xs: [u32]) -> u32 { + let mut sum = 0; + for x in xs { + sum = sum + x; + } + sum +} + +unconstrained fn brillig_slice_sum(xs: [u32]) -> u32 { + let mut sum = 0; + for x in xs { + sum = sum + x; + } + sum +} + +fn set_ref(c: &mut u32, b: &mut u32) { + *c = *b; +}