Skip to content

Commit

Permalink
chore(ssa refactor): Implement intrinsics (#1241)
Browse files Browse the repository at this point in the history
* Implement first-class functions

* Update crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs

Co-authored-by: kevaundray <kevtheappdev@gmail.com>

* Implement intrinsics

---------

Co-authored-by: kevaundray <kevtheappdev@gmail.com>
  • Loading branch information
jfecher and kevaundray authored Apr 27, 2023
1 parent 606b7d0 commit 06427e5
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 34 deletions.
19 changes: 17 additions & 2 deletions crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -71,6 +73,11 @@ pub(crate) struct DataFlowGraph {
/// will always have the same ValueId within this function.
functions: HashMap<FunctionId, ValueId>,

/// 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<Intrinsic, ValueId>,

/// Function signatures of external methods
signatures: DenseMap<Signature>,

Expand Down Expand Up @@ -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.
Expand Down
63 changes: 44 additions & 19 deletions crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,61 @@
use acvm::acir::BlackBoxFunc;

use super::{basic_block::BasicBlockId, map::Id, types::Type, value::ValueId};

/// Reference to an instruction
pub(crate) type InstructionId = Id<Instruction>;

#[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
/// special support for. (LowLevel)
/// - 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<Intrinsic> {
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.
Expand All @@ -41,10 +78,6 @@ pub(crate) enum Instruction {
/// Performs a function call with a list of its arguments.
Call { func: ValueId, arguments: Vec<ValueId> },

/// Performs a call to an intrinsic function and stores the
/// results in `return_arguments`.
Intrinsic { func: IntrinsicOpcodes, arguments: Vec<ValueId> },

/// 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.
///
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
}
}
}
Expand All @@ -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.
Expand Down
6 changes: 2 additions & 4 deletions crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
Expand Down Expand Up @@ -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 } => {
Expand Down
12 changes: 10 additions & 2 deletions crates/noirc_evaluator/src/ssa_refactor/ir/value.rs
Original file line number Diff line number Diff line change
@@ -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,
};

Expand Down Expand Up @@ -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 {
Expand All @@ -46,6 +53,7 @@ impl Value {
Value::Param { typ, .. } => *typ,
Value::NumericConstant { typ, .. } => *typ,
Value::Function { .. } => Type::Function,
Value::Intrinsic { .. } => Type::Function,
}
}
}
15 changes: 15 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<ValueId> {
Intrinsic::lookup(name)
.map(|intrinsic| self.current_function.dfg.import_intrinsic(intrinsic))
}
}
4 changes: 2 additions & 2 deletions crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

Expand Down
8 changes: 6 additions & 2 deletions crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
}
}
}
}

Expand Down
3 changes: 0 additions & 3 deletions crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -15,7 +14,6 @@ pub(super) enum Tree<T> {
#[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),
Expand All @@ -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"),
}
}
}
Expand Down

0 comments on commit 06427e5

Please sign in to comment.