diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 60591da311c..4d2ebe31efb 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -4,7 +4,9 @@ use super::{ basic_block::{BasicBlock, BasicBlockId}, constant::{NumericConstant, NumericConstantId}, function::{FunctionId, Signature}, - instruction::{Instruction, InstructionId, InstructionResultType, TerminatorInstruction}, + instruction::{ + Instruction, InstructionId, InstructionResultType, Intrinsic, TerminatorInstruction, + }, map::{DenseMap, Id, TwoWayMap}, types::Type, value::{Value, ValueId}, @@ -71,6 +73,11 @@ pub(crate) struct DataFlowGraph { /// will always have the same ValueId within this function. functions: HashMap, + /// Contains each intrinsic that has been imported into the current function. + /// This map is used to ensure that the ValueId for any given intrinsic is always + /// represented by only 1 ValueId within this function. + intrinsics: HashMap, + /// Function signatures of external methods signatures: DenseMap, @@ -162,7 +169,15 @@ impl DataFlowGraph { if let Some(existing) = self.functions.get(&function) { return *existing; } - self.values.insert(Value::Function { id: function }) + self.values.insert(Value::Function(function)) + } + + /// Gets or creates a ValueId for the given Intrinsic. + pub(crate) fn import_intrinsic(&mut self, intrinsic: Intrinsic) -> ValueId { + if let Some(existing) = self.intrinsics.get(&intrinsic) { + return *existing; + } + self.values.insert(Value::Intrinsic(intrinsic)) } /// Attaches results to the instruction, clearing any previous results. diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 5e9e7229e3a..756c7ae5a13 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -1,9 +1,10 @@ +use acvm::acir::BlackBoxFunc; + use super::{basic_block::BasicBlockId, map::Id, types::Type, value::ValueId}; /// Reference to an instruction pub(crate) type InstructionId = Id; -#[derive(Debug, PartialEq, Eq, Hash, Clone)] /// These are similar to built-ins in other languages. /// These can be classified under two categories: /// - Opcodes which the IR knows the target machine has @@ -11,14 +12,50 @@ pub(crate) type InstructionId = Id; /// - Opcodes which have no function definition in the /// source code and must be processed by the IR. An example /// of this is println. -pub(crate) struct IntrinsicOpcodes; +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub(crate) enum Intrinsic { + Sort, + Println, + ToBits(Endian), + ToRadix(Endian), + BlackBox(BlackBoxFunc), +} + +impl std::fmt::Display for Intrinsic { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Intrinsic::Println => write!(f, "println"), + Intrinsic::Sort => write!(f, "sort"), + Intrinsic::ToBits(Endian::Big) => write!(f, "to_be_bits"), + Intrinsic::ToBits(Endian::Little) => write!(f, "to_le_bits"), + Intrinsic::ToRadix(Endian::Big) => write!(f, "to_be_radix"), + Intrinsic::ToRadix(Endian::Little) => write!(f, "to_le_radix"), + Intrinsic::BlackBox(function) => write!(f, "{function}"), + } + } +} -impl std::fmt::Display for IntrinsicOpcodes { - fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!("intrinsics have no opcodes yet") +impl Intrinsic { + pub(crate) fn lookup(name: &str) -> Option { + match name { + "println" => Some(Intrinsic::Println), + "array_sort" => Some(Intrinsic::Sort), + "to_le_radix" => Some(Intrinsic::ToRadix(Endian::Little)), + "to_be_radix" => Some(Intrinsic::ToRadix(Endian::Big)), + "to_le_bits" => Some(Intrinsic::ToBits(Endian::Little)), + "to_be_bits" => Some(Intrinsic::ToBits(Endian::Big)), + other => BlackBoxFunc::lookup(other).map(Intrinsic::BlackBox), + } } } +/// The endian-ness of bits when encoding values as bits in e.g. ToBits or ToRadix +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub(crate) enum Endian { + Big, + Little, +} + #[derive(Debug, PartialEq, Eq, Hash, Clone)] /// Instructions are used to perform tasks. /// The instructions that the IR is able to specify are listed below. @@ -41,10 +78,6 @@ pub(crate) enum Instruction { /// Performs a function call with a list of its arguments. Call { func: ValueId, arguments: Vec }, - /// Performs a call to an intrinsic function and stores the - /// results in `return_arguments`. - Intrinsic { func: IntrinsicOpcodes, arguments: Vec }, - /// Allocates a region of memory. Note that this is not concerned with /// the type of memory, the type of element is determined when loading this memory. /// @@ -72,9 +105,6 @@ impl Instruction { Instruction::Constrain(_) => 0, // This returns 0 as the result depends on the function being called Instruction::Call { .. } => 0, - // This also returns 0, but we could get it a compile time, - // since we know the signatures for the intrinsics - Instruction::Intrinsic { .. } => 0, Instruction::Allocate { .. } => 1, Instruction::Load { .. } => 1, Instruction::Store { .. } => 0, @@ -94,9 +124,6 @@ impl Instruction { Instruction::Constrain(_) => 1, // This returns 0 as the arguments depend on the function being called Instruction::Call { .. } => 0, - // This also returns 0, but we could get it a compile time, - // since we know the function definition for the intrinsics - Instruction::Intrinsic { .. } => 0, Instruction::Allocate { size: _ } => 1, Instruction::Load { address: _ } => 1, Instruction::Store { address: _, value: _ } => 2, @@ -113,9 +140,7 @@ impl Instruction { InstructionResultType::Operand(*value) } Instruction::Constrain(_) | Instruction::Store { .. } => InstructionResultType::None, - Instruction::Load { .. } | Instruction::Call { .. } | Instruction::Intrinsic { .. } => { - InstructionResultType::Unknown - } + Instruction::Load { .. } | Instruction::Call { .. } => InstructionResultType::Unknown, } } } @@ -129,7 +154,7 @@ pub(crate) enum InstructionResultType { Known(Type), /// The result type of this function is unknown and separate from its operand types. - /// This occurs for function and intrinsic calls. + /// This occurs for function calls and load operations. Unknown, /// This instruction does not return any results. diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs index 4873f436dca..1471bd46e35 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs @@ -64,7 +64,8 @@ fn value(function: &Function, id: ValueId) -> String { let value = function.dfg[*constant].value(); format!("{} {}", typ, value) } - Value::Function { id } => id.to_string(), + Value::Function(id) => id.to_string(), + Value::Intrinsic(intrinsic) => intrinsic.to_string(), _ => id.to_string(), } } @@ -127,9 +128,6 @@ pub(crate) fn display_instruction( Instruction::Call { func, arguments } => { writeln!(f, "call {}({})", show(*func), value_list(function, arguments)) } - Instruction::Intrinsic { func, arguments } => { - writeln!(f, "intrinsic {func}({})", value_list(function, arguments)) - } Instruction::Allocate { size } => writeln!(f, "alloc {size} fields"), Instruction::Load { address } => writeln!(f, "load {}", show(*address)), Instruction::Store { address, value } => { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs index 39228ae655b..d7d8d8a41ab 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs @@ -1,7 +1,10 @@ use crate::ssa_refactor::ir::basic_block::BasicBlockId; use super::{ - constant::NumericConstantId, function::FunctionId, instruction::InstructionId, map::Id, + constant::NumericConstantId, + function::FunctionId, + instruction::{InstructionId, Intrinsic}, + map::Id, types::Type, }; @@ -36,7 +39,11 @@ pub(crate) enum Value { /// If the argument or return types are needed, users should retrieve /// their types via the Call instruction's arguments or the Call instruction's /// result types respectively. - Function { id: FunctionId }, + Function(FunctionId), + + /// An Intrinsic is a special kind of builtin function that may be handled internally + /// or optimized into a special form. + Intrinsic(Intrinsic), } impl Value { @@ -46,6 +53,7 @@ impl Value { Value::Param { typ, .. } => *typ, Value::NumericConstant { typ, .. } => *typ, Value::Function { .. } => Type::Function, + Value::Intrinsic { .. } => Type::Function, } } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs index 7da88e47157..6c407dfcd42 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs @@ -8,6 +8,8 @@ use crate::ssa_refactor::ir::{ value::{Value, ValueId}, }; +use super::ir::instruction::Intrinsic; + /// The per-function context for each ssa function being generated. /// /// This is split from the global SsaBuilder context to allow each function @@ -227,4 +229,17 @@ impl FunctionBuilder { // Clear the results of the previous load for safety self.current_function.dfg.make_instruction_results(instruction, None); } + + /// Returns a ValueId pointing to the given function or imports the function + /// into the current function if it was not already, and returns that ID. + pub(crate) fn import_function(&mut self, function: FunctionId) -> ValueId { + self.current_function.dfg.import_function(function) + } + + /// Retrieve a value reference to the given intrinsic operation. + /// Returns None if there is no intrinsic matching the given name. + pub(crate) fn import_intrinsic(&mut self, name: &str) -> Option { + Intrinsic::lookup(name) + .map(|intrinsic| self.current_function.dfg.import_intrinsic(intrinsic)) + } } 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 bd04f90d063..909ed4ff84d 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -242,9 +242,9 @@ impl<'a> FunctionContext<'a> { /// Retrieves the given function, adding it to the function queue /// if it is not yet compiled. - pub(super) fn get_or_queue_function(&self, id: FuncId) -> Values { + pub(super) fn get_or_queue_function(&mut self, id: FuncId) -> Values { let function = self.shared_context.get_or_queue_function(id); - Values::Leaf(super::value::Value::Function(function)) + self.builder.import_function(function).into() } } 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 8475b3c84c7..715f835ab7f 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -66,8 +66,12 @@ impl<'a> FunctionContext<'a> { match &ident.definition { ast::Definition::Local(id) => self.lookup(*id).map(|value| value.eval(self).into()), ast::Definition::Function(id) => self.get_or_queue_function(*id), - ast::Definition::Builtin(_) => todo!(), - ast::Definition::LowLevel(_) => todo!(), + ast::Definition::Builtin(name) | ast::Definition::LowLevel(name) => { + match self.builder.import_intrinsic(name) { + Some(builtin) => builtin.into(), + None => panic!("No builtin function named '{name}' found"), + } + } } } 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 52ff52d75f2..410e375fcd6 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs @@ -1,6 +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; @@ -15,7 +14,6 @@ pub(super) enum Tree { #[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), @@ -32,7 +30,6 @@ impl Value { let offset = ctx.builder.field_constant(0u128); ctx.builder.insert_load(address, offset, typ) } - Value::Function(_) => panic!("Tried to evaluate a function value"), } } }