Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(ssa refactor): Implement intrinsics #1241

Merged
merged 6 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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