From a63f874e81c2d8ceac5c71c63b64af1f5602958c Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 25 Apr 2023 17:02:04 -0500 Subject: [PATCH 1/7] Implement ssa-gen for if --- .../src/ssa_refactor/ir/cfg.rs | 23 ++-- .../src/ssa_refactor/ir/constant.rs | 4 +- .../src/ssa_refactor/ir/dfg.rs | 24 ++++- .../src/ssa_refactor/ir/function.rs | 2 +- .../src/ssa_refactor/ir/instruction.rs | 11 +- .../src/ssa_refactor/ir/printer.rs | 12 +-- .../ssa_builder/function_builder.rs | 101 ++++++++++++++++-- .../src/ssa_refactor/ssa_gen/context.rs | 12 ++- .../src/ssa_refactor/ssa_gen/mod.rs | 99 ++++++++++++----- .../src/ssa_refactor/ssa_gen/value.rs | 10 ++ .../src/monomorphization/ast.rs | 1 + .../src/monomorphization/mod.rs | 2 +- 12 files changed, 231 insertions(+), 70 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs index 05b64e30ed8..3e469361c37 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs @@ -133,29 +133,27 @@ mod tests { // Build function of form // fn func { // block0(cond: u1): - // jmpif cond(), then: block2, else: block1 + // jmpif cond, then: block2, else: block1 // block1(): - // jmpif cond(), then: block1, else: block2 + // jmpif cond, then: block1, else: block2 // block2(): - // return + // return () // } let mut func = Function::new("func".into()); let block0_id = func.entry_block(); let cond = func.dfg.add_block_parameter(block0_id, Type::unsigned(1)); - let block1_id = func.dfg.new_block(); - let block2_id = func.dfg.new_block(); + let block1_id = func.dfg.make_block(); + let block2_id = func.dfg.make_block(); func.dfg[block0_id].set_terminator(TerminatorInstruction::JmpIf { condition: cond, then_destination: block2_id, else_destination: block1_id, - arguments: vec![], }); func.dfg[block1_id].set_terminator(TerminatorInstruction::JmpIf { condition: cond, then_destination: block1_id, else_destination: block2_id, - arguments: vec![], }); func.dfg[block2_id].set_terminator(TerminatorInstruction::Return { return_values: vec![] }); @@ -192,15 +190,15 @@ mod tests { // Modify function to form: // fn func { // block0(cond: u1): - // jmpif cond(), then: block1, else: ret_block + // jmpif cond, then: block1, else: ret_block // block1(): - // jmpif cond(), then: block1, else: block2 + // jmpif cond, then: block1, else: block2 // block2(): - // jmp ret_block + // jmp ret_block() // ret_block(): - // return + // return () // } - let ret_block_id = func.dfg.new_block(); + let ret_block_id = func.dfg.make_block(); func.dfg[ret_block_id] .set_terminator(TerminatorInstruction::Return { return_values: vec![] }); func.dfg[block2_id].set_terminator(TerminatorInstruction::Jmp { @@ -211,7 +209,6 @@ mod tests { condition: cond, then_destination: block1_id, else_destination: ret_block_id, - arguments: vec![], }); // Recompute new and changed blocks diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs index 6d5538d3410..4c793a144da 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs @@ -16,8 +16,8 @@ impl NumericConstant { Self(value) } - pub(crate) fn value(&self) -> &FieldElement { - &self.0 + pub(crate) fn value(&self) -> FieldElement { + self.0 } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 54ffd5a05f6..46abbf22748 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -2,7 +2,7 @@ use super::{ basic_block::{BasicBlock, BasicBlockId}, constant::{NumericConstant, NumericConstantId}, function::Signature, - instruction::{Instruction, InstructionId, InstructionResultType}, + instruction::{Instruction, InstructionId, InstructionResultType, TerminatorInstruction}, map::{DenseMap, Id, SecondaryMap, TwoWayMap}, types::Type, value::{Value, ValueId}, @@ -75,14 +75,14 @@ impl DataFlowGraph { /// Creates a new basic block with no parameters. /// After being created, the block is unreachable in the current function /// until another block is made to jump to it. - pub(crate) fn new_block(&mut self) -> BasicBlockId { + pub(crate) fn make_block(&mut self) -> BasicBlockId { self.blocks.insert(BasicBlock::new(Vec::new())) } /// Creates a new basic block with the given parameters. /// After being created, the block is unreachable in the current function /// until another block is made to jump to it. - pub(crate) fn new_block_with_parameters( + pub(crate) fn make_block_with_parameters( &mut self, parameter_types: impl Iterator, ) -> BasicBlockId { @@ -230,6 +230,24 @@ impl DataFlowGraph { ) { self.blocks[block].insert_instruction(instruction); } + + /// Returns the field element represented by this value if it is a numeric constant. + /// Returns None if the given value is not a numeric constant. + pub(crate) fn get_numeric_constant(&self, value: Id) -> Option { + match self.values[value] { + Value::NumericConstant { constant, .. } => Some(self[constant].value()), + _ => None, + } + } + + /// Sets the terminator instruction for the given basic block + pub(crate) fn set_block_terminator( + &mut self, + block: BasicBlockId, + terminator: TerminatorInstruction, + ) { + self.blocks[block].set_terminator(terminator) + } } impl std::ops::Index for DataFlowGraph { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs index 63cd31142c4..1a735726029 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs @@ -32,7 +32,7 @@ impl Function { /// Note that any parameters to the function must be manually added later. pub(crate) fn new(name: String) -> Self { let mut dfg = DataFlowGraph::default(); - let entry_block = dfg.new_block(); + let entry_block = dfg.make_block(); Self { name, source_locations: SecondaryMap::new(), entry_block, dfg } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index dcab6e04006..11c6b8dc05f 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -150,14 +150,9 @@ pub(crate) enum TerminatorInstruction { /// /// Jump If /// - /// If the condition is true: jump to the specified `then_destination` with `arguments`. - /// Otherwise, jump to the specified `else_destination` with `arguments`. - JmpIf { - condition: ValueId, - then_destination: BasicBlockId, - else_destination: BasicBlockId, - arguments: Vec, - }, + /// If the condition is true: jump to the specified `then_destination`. + /// Otherwise, jump to the specified `else_destination`. + JmpIf { condition: ValueId, then_destination: BasicBlockId, else_destination: BasicBlockId }, /// Unconditional Jump /// diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs index 1a7737e97b0..a711482e08c 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs @@ -57,17 +57,11 @@ pub(crate) fn display_terminator( Some(TerminatorInstruction::Jmp { destination, arguments }) => { writeln!(f, " jmp {}({})", destination, value_list(arguments)) } - Some(TerminatorInstruction::JmpIf { - condition, - arguments, - then_destination, - else_destination, - }) => { - let args = value_list(arguments); + Some(TerminatorInstruction::JmpIf { condition, then_destination, else_destination }) => { writeln!( f, - " jmpif {}({}) then: {}, else: {}", - condition, args, then_destination, else_destination + " jmpif {} then: {}, else: {}", + condition, then_destination, else_destination ) } Some(TerminatorInstruction::Return { return_values }) => { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs index b30ff11c2e1..2bafce15b43 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs @@ -3,7 +3,7 @@ use acvm::FieldElement; use crate::ssa_refactor::ir::{ basic_block::BasicBlockId, function::{Function, FunctionId}, - instruction::{Binary, BinaryOp, Instruction}, + instruction::{Binary, BinaryOp, Instruction, TerminatorInstruction}, types::Type, value::ValueId, }; @@ -62,8 +62,12 @@ impl<'ssa> FunctionBuilder<'ssa> { } /// Insert a numeric constant into the current function - pub(crate) fn numeric_constant(&mut self, value: FieldElement, typ: Type) -> ValueId { - self.current_function.dfg.make_constant(value, typ) + pub(crate) fn numeric_constant( + &mut self, + value: impl Into, + typ: Type, + ) -> ValueId { + self.current_function.dfg.make_constant(value.into(), typ) } /// Insert a numeric constant into the current function of type Field @@ -71,6 +75,19 @@ impl<'ssa> FunctionBuilder<'ssa> { self.numeric_constant(value.into(), Type::field()) } + pub(crate) fn type_of_value(&self, value: ValueId) -> Type { + self.current_function.dfg.type_of_value(value) + } + + pub(crate) fn insert_block(&mut self) -> BasicBlockId { + self.current_function.dfg.make_block() + } + + pub(crate) fn add_block_parameter(&mut self, block: BasicBlockId, typ: Type) -> ValueId { + self.current_function.dfg.add_block_parameter(block, typ) + } + + /// Inserts a new instruction at the end of the current block and returns its results fn insert_instruction( &mut self, instruction: Instruction, @@ -81,6 +98,13 @@ impl<'ssa> FunctionBuilder<'ssa> { self.current_function.dfg.instruction_results(id) } + /// Switch to inserting instructions in the given block. + /// Expects the given block to be within the same function. If you want to insert + /// instructions into a new function, call new_function instead. + pub(crate) fn switch_to_block(&mut self, block: BasicBlockId) { + self.current_block = block; + } + /// Insert an allocate instruction at the end of the current block, allocating the /// given amount of field elements. Returns the result of the allocate instruction, /// which is always a Reference to the allocated data. @@ -88,16 +112,31 @@ impl<'ssa> FunctionBuilder<'ssa> { self.insert_instruction(Instruction::Allocate { size: size_to_allocate }, None)[0] } - /// Insert a Load instruction at the end of the current block, loading from the given address - /// which should point to a previous Allocate instruction. Note that this is limited to loading - /// a single value. Loading multiple values (such as a tuple) will require multiple loads. + /// Insert a Load instruction at the end of the current block, loading from the given offset + /// of the given address which should point to a previous Allocate instruction. Note that + /// this is limited to loading a single value. Loading multiple values (such as a tuple) + /// will require multiple loads. + /// 'offset' is in units of FieldElements here. So loading the fourth FieldElement stored in + /// an array will have an offset of 3. /// Returns the element that was loaded. - pub(crate) fn insert_load(&mut self, address: ValueId, type_to_load: Type) -> ValueId { + pub(crate) fn insert_load( + &mut self, + mut address: ValueId, + offset: ValueId, + type_to_load: Type, + ) -> ValueId { + if let Some(offset) = self.current_function.dfg.get_numeric_constant(offset) { + if !offset.is_zero() { + let offset = self.field_constant(offset); + address = self.insert_binary(address, BinaryOp::Add, offset); + } + }; self.insert_instruction(Instruction::Load { address }, Some(vec![type_to_load]))[0] } /// Insert a Store instruction at the end of the current block, storing the given element - /// at the given address. Expects that the address points to a previous Allocate instruction. + /// at the given address. Expects that the address points somewhere + /// within a previous Allocate instruction. pub(crate) fn insert_store(&mut self, address: ValueId, value: ValueId) { self.insert_instruction(Instruction::Store { address, value }, None); } @@ -119,4 +158,50 @@ impl<'ssa> FunctionBuilder<'ssa> { pub(crate) fn insert_not(&mut self, rhs: ValueId) -> ValueId { self.insert_instruction(Instruction::Not(rhs), None)[0] } + + /// Insert a cast instruction at the end of the current block. + /// Returns the result of the cast instruction. + pub(crate) fn insert_cast(&mut self, value: ValueId, typ: Type) -> ValueId { + self.insert_instruction(Instruction::Cast(value, typ), None)[0] + } + + /// Insert a constrain instruction at the end of the current block. + pub(crate) fn insert_constrain(&mut self, boolean: ValueId) { + self.insert_instruction(Instruction::Constrain(boolean), None); + } + + /// Terminates the current block with the given terminator instruction + fn terminate_block_with(&mut self, terminator: TerminatorInstruction) { + self.current_function.dfg.set_block_terminator(self.current_block, terminator); + } + + /// Terminate the current block with a jmp instruction to jmp to the given + /// block with the given arguments. + pub(crate) fn terminate_with_jmp( + &mut self, + destination: BasicBlockId, + arguments: Vec, + ) { + self.terminate_block_with(TerminatorInstruction::Jmp { destination, arguments }) + } + + /// Terminate the current block with a jmpif instruction to jmp with the given arguments + /// block with the given arguments. + pub(crate) fn terminate_with_jmpif( + &mut self, + condition: ValueId, + then_destination: BasicBlockId, + else_destination: BasicBlockId, + ) { + self.terminate_block_with(TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + }); + } + + /// Terminate the current block with a return instruction + pub(crate) fn terminate_with_return(&mut self, return_values: Vec) { + self.terminate_block_with(TerminatorInstruction::Return { return_values }); + } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs index f76a6675077..422ca54a38e 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::sync::{Mutex, RwLock}; +use acvm::FieldElement; use iter_extended::vecmap; use noirc_frontend::monomorphization::ast::{self, LocalId, Parameters}; use noirc_frontend::monomorphization::ast::{FuncId, Program}; @@ -129,7 +130,7 @@ impl<'a> FunctionContext<'a> { /// Insert a unit constant into the current function if not already /// present, and return its value pub(super) fn unit_value(&mut self) -> Values { - self.builder.numeric_constant(0u128.into(), Type::Unit).into() + self.builder.numeric_constant(0u128, Type::Unit).into() } /// Insert a binary instruction at the end of the current block. @@ -155,6 +156,15 @@ impl<'a> FunctionContext<'a> { } result.into() } + + /// Create a const offset of an address for an array load or store + pub(super) fn make_offset(&mut self, mut address: ValueId, offset: u128) -> ValueId { + if offset != 0 { + let offset = self.builder.field_constant(offset); + address = self.builder.insert_binary(address, BinaryOp::Add, offset) + } + address + } } /// True if the given operator cannot be encoded directly and needs diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs index 553b5eb2218..7963b7d915d 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -1,7 +1,6 @@ mod context; mod value; -use acvm::FieldElement; use context::SharedContext; use iter_extended::vecmap; use noirc_errors::Location; @@ -88,15 +87,11 @@ impl<'a> FunctionContext<'a> { self.builder.numeric_constant(*value, typ).into() } ast::Literal::Bool(value) => { - // Booleans are represented as u1s with 0 = false, 1 = true - let typ = Type::unsigned(1); - let value = FieldElement::from(*value as u128); - self.builder.numeric_constant(value, typ).into() + self.builder.numeric_constant(*value as u128, Type::bool()).into() } ast::Literal::Str(string) => { let elements = vecmap(string.as_bytes(), |byte| { - let value = FieldElement::from(*byte as u128); - self.builder.numeric_constant(value, Type::field()).into() + self.builder.numeric_constant(*byte as u128, Type::field()).into() }); self.codegen_array(elements, Tree::Leaf(Type::field())) } @@ -110,15 +105,10 @@ impl<'a> FunctionContext<'a> { })); // Now we must manually store all the elements into the array - let mut i = 0; + let mut i = 0u128; for element in elements { element.for_each(|value| { - let address = if i == 0 { - array - } else { - let offset = self.builder.field_constant(i as u128); - self.builder.insert_binary(array, BinaryOp::Add, offset) - }; + let address = self.make_offset(array, i); self.builder.insert_store(address, value.eval()); i += 1; }); @@ -135,8 +125,16 @@ impl<'a> FunctionContext<'a> { result } - fn codegen_unary(&mut self, _unary: &ast::Unary) -> Values { - todo!() + fn codegen_unary(&mut self, unary: &ast::Unary) -> Values { + let rhs = self.codegen_non_tuple_expression(&unary.rhs); + match unary.operator { + noirc_frontend::UnaryOp::Not => self.builder.insert_not(rhs).into(), + noirc_frontend::UnaryOp::Minus => { + let typ = self.builder.type_of_value(rhs); + let zero = self.builder.numeric_constant(0u128, typ); + self.builder.insert_binary(zero, BinaryOp::Sub, rhs).into() + } + } } fn codegen_binary(&mut self, binary: &ast::Binary) -> Values { @@ -145,20 +143,71 @@ impl<'a> FunctionContext<'a> { self.insert_binary(lhs, binary.operator, rhs) } - fn codegen_index(&mut self, _index: &ast::Index) -> Values { - todo!() + fn codegen_index(&mut self, index: &ast::Index) -> Values { + let array = self.codegen_non_tuple_expression(&index.collection); + let base_offset = self.codegen_non_tuple_expression(&index.index); + + // base_index = base_offset * type_size + let type_size = Self::convert_type(&index.element_type).size_of_type(); + let type_size = self.builder.field_constant(type_size as u128); + let base_index = self.builder.insert_binary(base_offset, BinaryOp::Mul, type_size); + + let mut field_index = 0u128; + self.map_type(&index.element_type, |ctx, typ| { + let offset = ctx.make_offset(base_index, field_index); + field_index += 1; + ctx.builder.insert_load(array, offset, typ).into() + }) } - fn codegen_cast(&mut self, _cast: &ast::Cast) -> Values { - todo!() + fn codegen_cast(&mut self, cast: &ast::Cast) -> Values { + let lhs = self.codegen_non_tuple_expression(&cast.lhs); + let typ = Self::convert_non_tuple_type(&cast.r#type); + self.builder.insert_cast(lhs, typ).into() } fn codegen_for(&mut self, _for_expr: &ast::For) -> Values { todo!() } - fn codegen_if(&mut self, _if_expr: &ast::If) -> Values { - todo!() + fn codegen_if(&mut self, if_expr: &ast::If) -> Values { + let condition = self.codegen_non_tuple_expression(&if_expr.condition); + + let then_block = self.builder.insert_block(); + let else_block = self.builder.insert_block(); + + self.builder.terminate_with_jmpif(condition, then_block, else_block); + + self.builder.switch_to_block(then_block); + let then_value = self.codegen_expression(&if_expr.consequence); + + let mut result = self.unit_value(); + + if let Some(alternative) = &if_expr.alternative { + self.builder.switch_to_block(else_block); + let else_value = self.codegen_expression(&alternative); + + let end_block = self.builder.insert_block(); + + // Create block arguments for the end block as needed to branch to + // with our then and else value. + result = self.map_type(&if_expr.typ, |ctx, typ| { + ctx.builder.add_block_parameter(end_block, typ).into() + }); + + self.builder.terminate_with_jmp(end_block, else_value.into_value_list()); + + // Must also set the then block to jmp to the end now + self.builder.switch_to_block(then_block); + self.builder.terminate_with_jmp(end_block, then_value.into_value_list()); + self.builder.switch_to_block(end_block); + } else { + // In the case we have no 'else', the 'else' block is actually the end block. + self.builder.terminate_with_jmp(else_block, vec![]); + self.builder.switch_to_block(else_block); + } + + result } fn codegen_tuple(&mut self, tuple: &[Expression]) -> Values { @@ -182,8 +231,10 @@ impl<'a> FunctionContext<'a> { todo!() } - fn codegen_constrain(&mut self, _constrain: &Expression, _location: Location) -> Values { - todo!() + fn codegen_constrain(&mut self, expr: &Expression, _location: Location) -> Values { + let boolean = self.codegen_non_tuple_expression(expr); + self.builder.insert_constrain(boolean); + self.unit_value() } fn codegen_assign(&mut self, _assign: &ast::Assign) -> Values { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs index 83a5d15c904..31a93374940 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs @@ -1,3 +1,5 @@ +use iter_extended::vecmap; + use crate::ssa_refactor::ir::function::FunctionId as IrFunctionId; use crate::ssa_refactor::ir::types::Type; use crate::ssa_refactor::ir::value::ValueId as IrValueId; @@ -76,3 +78,11 @@ impl Tree { self.count_leaves() } } + +impl Tree { + /// Flattens and evaluates this Tree into a list of ir values + /// for return statements, branching instructions, or function parameters. + pub(super) fn into_value_list(self) -> Vec { + vecmap(self.flatten(), Value::eval) + } +} diff --git a/crates/noirc_frontend/src/monomorphization/ast.rs b/crates/noirc_frontend/src/monomorphization/ast.rs index e4339c8e367..4101a2db566 100644 --- a/crates/noirc_frontend/src/monomorphization/ast.rs +++ b/crates/noirc_frontend/src/monomorphization/ast.rs @@ -131,6 +131,7 @@ pub struct Call { pub struct Index { pub collection: Box, pub index: Box, + pub element_type: Type, pub location: Location, } diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index bfce292d2eb..57fe9b1e97d 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -411,7 +411,7 @@ impl<'interner> Monomorphizer<'interner> { | ast::Type::Bool | ast::Type::Unit | ast::Type::Function(_, _) => { - ast::Expression::Index(ast::Index { collection, index, location }) + ast::Expression::Index(ast::Index { collection, index, element_type, location }) } ast::Type::Tuple(elements) => { From aa7308881dae2f8754da3bd9720fcfb43c120b66 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 25 Apr 2023 17:06:25 -0500 Subject: [PATCH 2/7] Satisfy the clippy gods --- crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs | 2 +- .../src/ssa_refactor/ssa_builder/function_builder.rs | 2 +- crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs | 3 +-- crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 46abbf22748..8acce876d90 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -246,7 +246,7 @@ impl DataFlowGraph { block: BasicBlockId, terminator: TerminatorInstruction, ) { - self.blocks[block].set_terminator(terminator) + self.blocks[block].set_terminator(terminator); } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs index 2bafce15b43..c0a94be6f80 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs @@ -182,7 +182,7 @@ impl<'ssa> FunctionBuilder<'ssa> { destination: BasicBlockId, arguments: Vec, ) { - self.terminate_block_with(TerminatorInstruction::Jmp { destination, arguments }) + self.terminate_block_with(TerminatorInstruction::Jmp { destination, arguments }); } /// Terminate the current block with a jmpif instruction to jmp with the given arguments diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs index 422ca54a38e..30855b8fdc8 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::sync::{Mutex, RwLock}; -use acvm::FieldElement; use iter_extended::vecmap; use noirc_frontend::monomorphization::ast::{self, LocalId, Parameters}; use noirc_frontend::monomorphization::ast::{FuncId, Program}; @@ -161,7 +160,7 @@ impl<'a> FunctionContext<'a> { pub(super) fn make_offset(&mut self, mut address: ValueId, offset: u128) -> ValueId { if offset != 0 { let offset = self.builder.field_constant(offset); - address = self.builder.insert_binary(address, BinaryOp::Add, offset) + address = self.builder.insert_binary(address, BinaryOp::Add, offset); } address } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs index 7963b7d915d..04fb88d76d0 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -185,7 +185,7 @@ impl<'a> FunctionContext<'a> { if let Some(alternative) = &if_expr.alternative { self.builder.switch_to_block(else_block); - let else_value = self.codegen_expression(&alternative); + let else_value = self.codegen_expression(alternative); let end_block = self.builder.insert_block(); From 92641a92b932edef398f15ce68e5d3415464847f Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 26 Apr 2023 08:47:53 -0500 Subject: [PATCH 3/7] Fix printing bug --- .../noirc_evaluator/src/ssa_refactor/ir/map.rs | 16 ++++++++++++++-- .../src/ssa_refactor/ir/printer.rs | 18 ++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs index 5937b374726..24b30241293 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs @@ -69,9 +69,21 @@ impl std::fmt::Debug for Id { } } -impl std::fmt::Display for Id { +impl std::fmt::Display for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "${}", self.index) + write!(f, "b{}", self.index) + } +} + +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "v{}", self.index) + } +} + +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "f{}", self.index) } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs index a711482e08c..feb363fbdbe 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs @@ -1,5 +1,8 @@ //! This file is for pretty-printing the SSA IR in a human-readable form for debugging. -use std::fmt::{Formatter, Result}; +use std::{ + collections::HashSet, + fmt::{Formatter, Result}, +}; use iter_extended::vecmap; @@ -12,19 +15,26 @@ use super::{ pub(crate) fn display_function(function: &Function, f: &mut Formatter) -> Result { writeln!(f, "fn {} {{", function.name)?; - display_block_with_successors(function, function.entry_block, f)?; + display_block_with_successors(function, function.entry_block, &mut HashSet::new(), f)?; write!(f, "}}") } +/// Displays a block followed by all of its successors recursively. +/// This uses a HashSet to keep track of the visited blocks. Otherwise, +/// there would be infinite recursion for any loops in the IR. pub(crate) fn display_block_with_successors( function: &Function, block_id: BasicBlockId, + visited: &mut HashSet, f: &mut Formatter, ) -> Result { display_block(function, block_id, f)?; + visited.insert(block_id); for successor in function.dfg[block_id].successors() { - display_block(function, successor, f)?; + if !visited.contains(&successor) { + display_block_with_successors(function, successor, visited, f)?; + } } Ok(()) } @@ -36,7 +46,7 @@ pub(crate) fn display_block( ) -> Result { let block = &function.dfg[block_id]; - writeln!(f, "{}({}):", block_id, value_list(block.parameters()))?; + writeln!(f, " {}({}):", block_id, value_list(block.parameters()))?; for instruction in block.instructions() { display_instruction(function, *instruction, f)?; From a0b6f536785de502ed093a6ff105a8993893976e Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 26 Apr 2023 10:41:44 -0500 Subject: [PATCH 4/7] Print constants directly --- .../src/ssa_refactor/ir/dfg.rs | 11 ++++- .../src/ssa_refactor/ir/printer.rs | 47 ++++++++++++------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 8acce876d90..9b713eee06e 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -234,8 +234,17 @@ impl DataFlowGraph { /// Returns the field element represented by this value if it is a numeric constant. /// Returns None if the given value is not a numeric constant. pub(crate) fn get_numeric_constant(&self, value: Id) -> Option { + self.get_numeric_constant_with_type(value).map(|(value, _typ)| value) + } + + /// Returns the field element and type represented by this value if it is a numeric constant. + /// Returns None if the given value is not a numeric constant. + pub(crate) fn get_numeric_constant_with_type( + &self, + value: Id, + ) -> Option<(FieldElement, Type)> { match self.values[value] { - Value::NumericConstant { constant, .. } => Some(self[constant].value()), + Value::NumericConstant { constant, typ } => Some((self[constant].value(), typ)), _ => None, } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs index feb363fbdbe..57c573c7bd4 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs @@ -46,26 +46,36 @@ pub(crate) fn display_block( ) -> Result { let block = &function.dfg[block_id]; - writeln!(f, " {}({}):", block_id, value_list(block.parameters()))?; + writeln!(f, " {}({}):", block_id, value_list(function, block.parameters()))?; for instruction in block.instructions() { display_instruction(function, *instruction, f)?; } - display_terminator(block.terminator(), f) + display_terminator(function, block.terminator(), f) } -fn value_list(values: &[ValueId]) -> String { - vecmap(values, ToString::to_string).join(", ") +/// Specialize displaying value ids so that if they refer to constants we +/// print the constant directly +fn value(function: &Function, id: ValueId) -> String { + match function.dfg.get_numeric_constant_with_type(id) { + Some((value, typ)) => format!("{} {}", value, typ), + None => id.to_string(), + } +} + +fn value_list(function: &Function, values: &[ValueId]) -> String { + vecmap(values, |id| value(function, *id)).join(", ") } pub(crate) fn display_terminator( + function: &Function, terminator: Option<&TerminatorInstruction>, f: &mut Formatter, ) -> Result { match terminator { Some(TerminatorInstruction::Jmp { destination, arguments }) => { - writeln!(f, " jmp {}({})", destination, value_list(arguments)) + writeln!(f, " jmp {}({})", destination, value_list(function, arguments)) } Some(TerminatorInstruction::JmpIf { condition, then_destination, else_destination }) => { writeln!( @@ -75,7 +85,7 @@ pub(crate) fn display_terminator( ) } Some(TerminatorInstruction::Return { return_values }) => { - writeln!(f, " return {}", value_list(return_values)) + writeln!(f, " return {}", value_list(function, return_values)) } None => writeln!(f, " (no terminator instruction)"), } @@ -91,29 +101,34 @@ pub(crate) fn display_instruction( let results = function.dfg.instruction_results(instruction); if !results.is_empty() { - write!(f, "{} = ", value_list(results))?; + write!(f, "{} = ", value_list(function, results))?; } + let show = |id| value(function, id); + match &function.dfg[instruction] { Instruction::Binary(binary) => { - writeln!(f, "{} {}, {}", binary.operator, binary.lhs, binary.rhs) + writeln!(f, "{} {}, {}", binary.operator, show(binary.lhs), show(binary.rhs)) } - Instruction::Cast(value, typ) => writeln!(f, "cast {value} as {typ}"), - Instruction::Not(value) => writeln!(f, "not {value}"), + Instruction::Cast(lhs, typ) => writeln!(f, "cast {} as {typ}", show(*lhs)), + Instruction::Not(rhs) => writeln!(f, "not {}", show(*rhs)), Instruction::Truncate { value, bit_size, max_bit_size } => { - writeln!(f, "truncate {value} to {bit_size} bits, max_bit_size: {max_bit_size}") + let value = show(*value); + writeln!(f, "truncate {value} to {bit_size} bits, max_bit_size: {max_bit_size}",) } Instruction::Constrain(value) => { - writeln!(f, "constrain {value}") + writeln!(f, "constrain {}", show(*value)) } Instruction::Call { func, arguments } => { - writeln!(f, "call {func}({})", value_list(arguments)) + writeln!(f, "call {func}({})", value_list(function, arguments)) } Instruction::Intrinsic { func, arguments } => { - writeln!(f, "intrinsic {func}({})", value_list(arguments)) + writeln!(f, "intrinsic {func}({})", value_list(function, arguments)) } Instruction::Allocate { size } => writeln!(f, "alloc {size} fields"), - Instruction::Load { address } => writeln!(f, "load {address}"), - Instruction::Store { address, value } => writeln!(f, "store {value} at {address}"), + Instruction::Load { address } => writeln!(f, "load {}", show(*address)), + Instruction::Store { address, value } => { + writeln!(f, "store {} at {}", show(*address), show(*value)) + } } } From 4ceb47c626861e19637fb6a41914e41d05735af9 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 26 Apr 2023 10:56:59 -0500 Subject: [PATCH 5/7] Impl for loops --- .../src/ssa_refactor/ssa_gen/context.rs | 5 ++++ .../src/ssa_refactor/ssa_gen/mod.rs | 30 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs index 30855b8fdc8..48175ebb52b 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -164,6 +164,11 @@ impl<'a> FunctionContext<'a> { } address } + + pub(super) fn define(&mut self, id: LocalId, value: Values) { + let existing = self.definitions.insert(id, value); + assert!(existing.is_none(), "Variable {id:?} was defined twice in ssa-gen pass"); + } } /// True if the given operator cannot be encoded directly and needs diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs index 04fb88d76d0..f8faf8eeeb4 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -166,8 +166,34 @@ impl<'a> FunctionContext<'a> { self.builder.insert_cast(lhs, typ).into() } - fn codegen_for(&mut self, _for_expr: &ast::For) -> Values { - todo!() + fn codegen_for(&mut self, for_expr: &ast::For) -> Values { + let loop_entry = self.builder.insert_block(); + let loop_body = self.builder.insert_block(); + let loop_end = self.builder.insert_block(); + + // this is the 'i' in `for i in start .. end { block }` + let loop_index = self.builder.add_block_parameter(loop_entry, Type::field()); + + let start_index = self.codegen_non_tuple_expression(&for_expr.start_range); + let end_index = self.codegen_non_tuple_expression(&for_expr.end_range); + + self.builder.terminate_with_jmp(loop_entry, vec![start_index]); + + // Compile the loop entry block + self.builder.switch_to_block(loop_entry); + let jump_condition = self.builder.insert_binary(loop_index, BinaryOp::Lt, end_index); + self.builder.terminate_with_jmpif(jump_condition, loop_body, loop_end); + + // Compile the loop body + self.builder.switch_to_block(loop_body); + self.define(for_expr.index_variable, loop_index.into()); + self.codegen_expression(&for_expr.block); + let new_loop_index = self.make_offset(loop_index, 1); + self.builder.terminate_with_jmp(loop_entry, vec![new_loop_index]); + + // Finish by switching back to the end of the loop + self.builder.switch_to_block(loop_end); + self.unit_value() } fn codegen_if(&mut self, if_expr: &ast::If) -> Values { From 483bb9c017b4bd38f0d1187cf5764e086a94d65c Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 26 Apr 2023 12:54:37 -0500 Subject: [PATCH 6/7] Implement immutable and mutable variables --- .../src/ssa_refactor/ir/dfg.rs | 16 +++- .../ssa_builder/function_builder.rs | 28 +++++- .../src/ssa_refactor/ssa_gen/context.rs | 46 ++++++++++ .../src/ssa_refactor/ssa_gen/mod.rs | 92 +++++++++++++------ .../src/ssa_refactor/ssa_gen/value.rs | 54 +++++++++-- 5 files changed, 201 insertions(+), 35 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 9b713eee06e..ab2018b1df8 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -126,6 +126,17 @@ impl DataFlowGraph { id } + /// Replace an instruction id with another. + /// + /// This function should generally be avoided if possible in favor of inserting new + /// instructions since it does not check whether the instruction results of the removed + /// instruction are still in use. Users of this function thus need to ensure the old + /// instruction's results are no longer in use or are otherwise compatible with the + /// new instruction's result count and types. + pub(crate) fn replace_instruction(&mut self, id: Id, instruction: Instruction) { + self.instructions[id] = instruction; + } + /// Insert a value into the dfg's storage and return an id to reference it. /// Until the value is used in an instruction it is unreachable. pub(crate) fn make_value(&mut self, value: Value) -> ValueId { @@ -141,8 +152,11 @@ impl DataFlowGraph { /// Attaches results to the instruction, clearing any previous results. /// + /// This does not normally need to be called manually as it is called within + /// make_instruction automatically. + /// /// Returns the results of the instruction - fn make_instruction_results( + pub(crate) fn make_instruction_results( &mut self, instruction_id: InstructionId, ctrl_typevars: Option>, diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs index c0a94be6f80..d11e9a763cd 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs @@ -5,7 +5,7 @@ use crate::ssa_refactor::ir::{ function::{Function, FunctionId}, instruction::{Binary, BinaryOp, Instruction, TerminatorInstruction}, types::Type, - value::ValueId, + value::{Value, ValueId}, }; use super::SharedBuilderContext; @@ -204,4 +204,30 @@ impl<'ssa> FunctionBuilder<'ssa> { pub(crate) fn terminate_with_return(&mut self, return_values: Vec) { self.terminate_block_with(TerminatorInstruction::Return { return_values }); } + + /// Mutates a load instruction into a store instruction. + /// + /// This function is used while generating ssa-form for assignments currently. + /// To re-use most of the expression infrastructure, the lvalue of an assignment + /// is compiled as an expression and to assign to it we replace the final load + /// (which should always be present to load a mutable value) with a store of the + /// assigned value. + pub(crate) fn mutate_load_into_store(&mut self, load_result: ValueId, value_to_store: ValueId) { + let (instruction, address) = match &self.current_function.dfg[load_result] { + Value::Instruction { instruction, .. } => { + match &self.current_function.dfg[*instruction] { + Instruction::Load { address } => (*instruction, *address), + other => { + panic!("mutate_load_into_store: Expected Load instruction, found {other:?}") + } + } + } + other => panic!("mutate_load_into_store: Expected Load instruction, found {other:?}"), + }; + + let store = Instruction::Store { address, value: value_to_store }; + self.current_function.dfg.replace_instruction(instruction, store); + // Clear the results of the previous load for safety + self.current_function.dfg.make_instruction_results(instruction, None); + } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs index 48175ebb52b..10206e28c2d 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -21,6 +21,7 @@ type FunctionQueue = Vec<(ast::FuncId, IrFunctionId)>; pub(super) struct FunctionContext<'a> { definitions: HashMap, + pub(super) builder: FunctionBuilder<'a>, shared_context: &'a SharedContext, } @@ -165,10 +166,55 @@ impl<'a> FunctionContext<'a> { address } + /// Define a local variable to be some Values that can later be retrieved + /// by calling self.lookup(id) pub(super) fn define(&mut self, id: LocalId, value: Values) { let existing = self.definitions.insert(id, value); assert!(existing.is_none(), "Variable {id:?} was defined twice in ssa-gen pass"); } + + /// Looks up the value of a given local variable. Expects the variable to have + /// been previously defined or panics otherwise. + pub(super) fn lookup(&self, id: LocalId) -> Values { + self.definitions.get(&id).expect("lookup: variable not defined").clone() + } + + /// Extract the given field of the tuple. Panics if the given Values is not + /// a Tree::Branch or does not have enough fields. + pub(super) fn get_field(tuple: Values, field_index: usize) -> Values { + match tuple { + Tree::Branch(mut trees) => trees.remove(field_index), + Tree::Leaf(value) => { + unreachable!("Tried to extract tuple index {field_index} from non-tuple {value:?}") + } + } + } + + /// Mutate lhs to equal rhs + pub(crate) fn assign(&mut self, lhs: Values, rhs: Values) { + match (lhs, rhs) { + (Tree::Branch(lhs_branches), Tree::Branch(rhs_branches)) => { + assert_eq!(lhs_branches.len(), rhs_branches.len()); + + for (lhs, rhs) in lhs_branches.into_iter().zip(rhs_branches) { + self.assign(lhs, rhs); + } + } + (Tree::Leaf(lhs), Tree::Leaf(rhs)) => { + // Re-evaluating these should have no effect + let (lhs, rhs) = (lhs.eval(self), rhs.eval(self)); + + // Expect lhs to be previously evaluated. If it is a load we need to undo + // the load to get the address to store to. + self.builder.mutate_load_into_store(lhs, rhs); + } + (lhs, rhs) => { + unreachable!( + "assign: Expected lhs and rhs values to match but found {lhs:?} and {rhs:?}" + ) + } + } + } } /// True if the given operator cannot be encoded directly and needs diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs index f8faf8eeeb4..14417711c40 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -63,16 +63,16 @@ impl<'a> FunctionContext<'a> { /// Codegen any non-tuple expression so that we can unwrap the Values /// tree to return a single value for use with most SSA instructions. fn codegen_non_tuple_expression(&mut self, expr: &Expression) -> ValueId { - match self.codegen_expression(expr) { - Tree::Branch(branches) => { - panic!("codegen_non_tuple_expression called on tuple {branches:?}") - } - Tree::Leaf(value) => value.eval(), - } + self.codegen_expression(expr).into_leaf().eval(self) } - fn codegen_ident(&mut self, _ident: &ast::Ident) -> Values { - todo!() + fn codegen_ident(&mut self, ident: &ast::Ident) -> Values { + match &ident.definition { + ast::Definition::Local(id) => self.lookup(*id).map(|value| value.eval(self).into()), + ast::Definition::Function(_) => todo!(), + ast::Definition::Builtin(_) => todo!(), + ast::Definition::LowLevel(_) => todo!(), + } } fn codegen_literal(&mut self, literal: &ast::Literal) -> Values { @@ -107,9 +107,10 @@ impl<'a> FunctionContext<'a> { // Now we must manually store all the elements into the array let mut i = 0u128; for element in elements { - element.for_each(|value| { + element.for_each(|element| { let address = self.make_offset(array, i); - self.builder.insert_store(address, value.eval()); + let element = element.eval(self); + self.builder.insert_store(address, element); i += 1; }); } @@ -145,15 +146,26 @@ impl<'a> FunctionContext<'a> { fn codegen_index(&mut self, index: &ast::Index) -> Values { let array = self.codegen_non_tuple_expression(&index.collection); - let base_offset = self.codegen_non_tuple_expression(&index.index); + self.codegen_array_index(array, &index.index, &index.element_type) + } + + /// This is broken off from codegen_index so that it can also be + /// used to codegen a LValue::Index + fn codegen_array_index( + &mut self, + array: super::ir::value::ValueId, + index: &ast::Expression, + element_type: &ast::Type, + ) -> Values { + let base_offset = self.codegen_non_tuple_expression(index); // base_index = base_offset * type_size - let type_size = Self::convert_type(&index.element_type).size_of_type(); + let type_size = Self::convert_type(element_type).size_of_type(); let type_size = self.builder.field_constant(type_size as u128); let base_index = self.builder.insert_binary(base_offset, BinaryOp::Mul, type_size); let mut field_index = 0u128; - self.map_type(&index.element_type, |ctx, typ| { + self.map_type(element_type, |ctx, typ| { let offset = ctx.make_offset(base_index, field_index); field_index += 1; ctx.builder.insert_load(array, offset, typ).into() @@ -221,11 +233,13 @@ impl<'a> FunctionContext<'a> { ctx.builder.add_block_parameter(end_block, typ).into() }); - self.builder.terminate_with_jmp(end_block, else_value.into_value_list()); + let else_values = else_value.into_value_list(self); + self.builder.terminate_with_jmp(end_block, else_values); // Must also set the then block to jmp to the end now self.builder.switch_to_block(then_block); - self.builder.terminate_with_jmp(end_block, then_value.into_value_list()); + let then_values = then_value.into_value_list(self); + self.builder.terminate_with_jmp(end_block, then_values); self.builder.switch_to_block(end_block); } else { // In the case we have no 'else', the 'else' block is actually the end block. @@ -240,21 +254,30 @@ impl<'a> FunctionContext<'a> { Tree::Branch(vecmap(tuple, |expr| self.codegen_expression(expr))) } - fn codegen_extract_tuple_field(&mut self, tuple: &Expression, index: usize) -> Values { - match self.codegen_expression(tuple) { - Tree::Branch(mut trees) => trees.remove(index), - Tree::Leaf(value) => { - unreachable!("Tried to extract tuple index {index} from non-tuple {value:?}") - } - } + fn codegen_extract_tuple_field(&mut self, tuple: &Expression, field_index: usize) -> Values { + let tuple = self.codegen_expression(tuple); + Self::get_field(tuple, field_index) } fn codegen_call(&mut self, _call: &ast::Call) -> Values { todo!() } - fn codegen_let(&mut self, _let_expr: &ast::Let) -> Values { - todo!() + fn codegen_let(&mut self, let_expr: &ast::Let) -> Values { + let mut values = self.codegen_expression(&let_expr.expression); + + if let_expr.mutable { + values.map_mut(|value| { + let value = value.eval(self); + // Size is always 1 here since we're recursively unpacking tuples + let alloc = self.builder.insert_allocate(1); + self.builder.insert_store(alloc, value); + alloc.into() + }); + } + + self.define(let_expr.id, values); + self.unit_value() } fn codegen_constrain(&mut self, expr: &Expression, _location: Location) -> Values { @@ -263,8 +286,25 @@ impl<'a> FunctionContext<'a> { self.unit_value() } - fn codegen_assign(&mut self, _assign: &ast::Assign) -> Values { - todo!() + fn codegen_assign(&mut self, assign: &ast::Assign) -> Values { + let lhs = self.codegen_lvalue(&assign.lvalue); + let rhs = self.codegen_expression(&assign.expression); + self.assign(lhs, rhs); + self.unit_value() + } + + fn codegen_lvalue(&mut self, lvalue: &ast::LValue) -> Values { + match lvalue { + ast::LValue::Ident(ident) => self.codegen_ident(ident), + ast::LValue::Index { array, index, element_type, location: _ } => { + let array = self.codegen_lvalue(&array).into_leaf().eval(self); + self.codegen_array_index(array, &index, element_type) + } + ast::LValue::MemberAccess { object, field_index } => { + let object = self.codegen_lvalue(&object); + Self::get_field(object, *field_index) + } + } } fn codegen_semi(&mut self, expr: &Expression) -> Values { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs index 31a93374940..459e41bd4c9 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs @@ -4,25 +4,34 @@ use crate::ssa_refactor::ir::function::FunctionId as IrFunctionId; use crate::ssa_refactor::ir::types::Type; use crate::ssa_refactor::ir::value::ValueId as IrValueId; -#[derive(Debug)] +use super::context::FunctionContext; + +#[derive(Debug, Clone)] pub(super) enum Tree { Branch(Vec>), Leaf(T), } -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub(super) enum Value { Normal(IrValueId), Function(IrFunctionId), + + /// A mutable variable that must be loaded as the given type before being used + Mutable(IrValueId, Type), } impl Value { /// Evaluate a value, returning an IrValue from it. - /// This has no effect on Value::Normal, but any variables will be updated with their latest - /// use. - pub(super) fn eval(self) -> IrValueId { + /// This has no effect on Value::Normal, but any variables will + /// need to be loaded from memory + pub(super) fn eval(self, ctx: &mut FunctionContext) -> IrValueId { match self { Value::Normal(value) => value, + Value::Mutable(address, typ) => { + let offset = ctx.builder.field_constant(0u128); + ctx.builder.insert_load(address, offset, typ) + } Value::Function(_) => panic!("Tried to evaluate a function value"), } } @@ -56,6 +65,37 @@ impl Tree { Tree::Leaf(value) => f(value), } } + + pub(super) fn map_mut(&mut self, mut f: impl FnMut(&T) -> Tree) { + self.map_mut_helper(&mut f) + } + + fn map_mut_helper(&mut self, f: &mut impl FnMut(&T) -> Tree) { + match self { + Tree::Branch(trees) => trees.into_iter().for_each(|tree| tree.map_mut_helper(f)), + Tree::Leaf(value) => *self = f(value), + } + } + + pub(super) fn map(self, mut f: impl FnMut(T) -> Tree) -> Tree { + self.map_helper(&mut f) + } + + fn map_helper(self, f: &mut impl FnMut(T) -> Tree) -> Tree { + match self { + Tree::Branch(trees) => Tree::Branch(vecmap(trees, |tree| tree.map_helper(f))), + Tree::Leaf(value) => f(value), + } + } + + /// Unwraps this Tree into the value of the leaf node. Panics if + /// this Tree is a Branch + pub(super) fn into_leaf(self) -> T { + match self { + Tree::Branch(_) => panic!("into_leaf called on a Tree::Branch"), + Tree::Leaf(value) => value, + } + } } impl From for Values { @@ -82,7 +122,7 @@ impl Tree { impl Tree { /// Flattens and evaluates this Tree into a list of ir values /// for return statements, branching instructions, or function parameters. - pub(super) fn into_value_list(self) -> Vec { - vecmap(self.flatten(), Value::eval) + pub(super) fn into_value_list(self, ctx: &mut FunctionContext) -> Vec { + vecmap(self.flatten(), |value| value.eval(ctx)) } } From a9f3f203d2c33b902bba07b716310009ef7b7598 Mon Sep 17 00:00:00 2001 From: jfecher Date: Wed, 26 Apr 2023 15:57:02 -0400 Subject: [PATCH 7/7] chore(ssa refactor): Implement for loops (#1233) Impl for loops --- .../src/ssa_refactor/ssa_gen/context.rs | 5 ++++ .../src/ssa_refactor/ssa_gen/mod.rs | 30 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs index 30855b8fdc8..48175ebb52b 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -164,6 +164,11 @@ impl<'a> FunctionContext<'a> { } address } + + pub(super) fn define(&mut self, id: LocalId, value: Values) { + let existing = self.definitions.insert(id, value); + assert!(existing.is_none(), "Variable {id:?} was defined twice in ssa-gen pass"); + } } /// True if the given operator cannot be encoded directly and needs diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs index 04fb88d76d0..f8faf8eeeb4 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -166,8 +166,34 @@ impl<'a> FunctionContext<'a> { self.builder.insert_cast(lhs, typ).into() } - fn codegen_for(&mut self, _for_expr: &ast::For) -> Values { - todo!() + fn codegen_for(&mut self, for_expr: &ast::For) -> Values { + let loop_entry = self.builder.insert_block(); + let loop_body = self.builder.insert_block(); + let loop_end = self.builder.insert_block(); + + // this is the 'i' in `for i in start .. end { block }` + let loop_index = self.builder.add_block_parameter(loop_entry, Type::field()); + + let start_index = self.codegen_non_tuple_expression(&for_expr.start_range); + let end_index = self.codegen_non_tuple_expression(&for_expr.end_range); + + self.builder.terminate_with_jmp(loop_entry, vec![start_index]); + + // Compile the loop entry block + self.builder.switch_to_block(loop_entry); + let jump_condition = self.builder.insert_binary(loop_index, BinaryOp::Lt, end_index); + self.builder.terminate_with_jmpif(jump_condition, loop_body, loop_end); + + // Compile the loop body + self.builder.switch_to_block(loop_body); + self.define(for_expr.index_variable, loop_index.into()); + self.codegen_expression(&for_expr.block); + let new_loop_index = self.make_offset(loop_index, 1); + self.builder.terminate_with_jmp(loop_entry, vec![new_loop_index]); + + // Finish by switching back to the end of the loop + self.builder.switch_to_block(loop_end); + self.unit_value() } fn codegen_if(&mut self, if_expr: &ast::If) -> Values {