From 1c3bab4585b6e379951364d41469d32cae7ff8fe Mon Sep 17 00:00:00 2001
From: Aaron Councilman <aaronjc4@illinois.edu>
Date: Tue, 10 Sep 2024 15:06:27 -0500
Subject: [PATCH] Add intrinsic calls to Hercules IR

---
 hercules_ir/src/build.rs     |  11 +++
 hercules_ir/src/def_use.rs   |   8 ++
 hercules_ir/src/dot.rs       |   6 ++
 hercules_ir/src/ir.rs        | 156 ++++++++++++++++++++++++++++++++++-
 hercules_ir/src/parse.rs     |  36 ++++++++
 hercules_ir/src/typecheck.rs | 129 +++++++++++++++++++++++++++++
 hercules_opt/src/ccp.rs      | 155 ++++++++++++++++++++++++++++++++++
 7 files changed, 500 insertions(+), 1 deletion(-)

diff --git a/hercules_ir/src/build.rs b/hercules_ir/src/build.rs
index 50a38ef8..1d6c4754 100644
--- a/hercules_ir/src/build.rs
+++ b/hercules_ir/src/build.rs
@@ -549,6 +549,17 @@ impl NodeBuilder {
         };
     }
 
+    pub fn build_intrinsic(
+        &mut self,
+        intrinsic : Intrinsic,
+        args: Box<[NodeID]>,
+    ) {
+        self.node = Node::IntrinsicCall {
+            intrinsic,
+            args,
+        };
+    }
+
     pub fn build_read(&mut self, collect: NodeID, indices: Box<[Index]>) {
         self.node = Node::Read { collect, indices };
     }
diff --git a/hercules_ir/src/def_use.rs b/hercules_ir/src/def_use.rs
index 36f21f03..a9c6f6e9 100644
--- a/hercules_ir/src/def_use.rs
+++ b/hercules_ir/src/def_use.rs
@@ -173,6 +173,10 @@ pub fn get_uses<'a>(node: &'a Node) -> NodeUses<'a> {
             dynamic_constants: _,
             args,
         } => NodeUses::Variable(args),
+        Node::IntrinsicCall {
+            intrinsic: _,
+            args,
+        } => NodeUses::Variable(args),
         Node::Read { collect, indices } => {
             let mut uses = vec![];
             for index in indices.iter() {
@@ -258,6 +262,10 @@ pub fn get_uses_mut<'a>(node: &'a mut Node) -> NodeUsesMut<'a> {
             dynamic_constants: _,
             args,
         } => NodeUsesMut::Variable(args.iter_mut().collect()),
+        Node::IntrinsicCall {
+            intrinsic: _,
+            args,
+        } => NodeUsesMut::Variable(args.iter_mut().collect()),
         Node::Read { collect, indices } => {
             let mut uses = vec![];
             for index in indices.iter_mut() {
diff --git a/hercules_ir/src/dot.rs b/hercules_ir/src/dot.rs
index 32f3a578..92603712 100644
--- a/hercules_ir/src/dot.rs
+++ b/hercules_ir/src/dot.rs
@@ -342,6 +342,12 @@ fn write_node<W: Write>(
                 module.write_dynamic_constant(*dc_id, &mut suffix)?;
             }
         }
+        Node::IntrinsicCall {
+            intrinsic,
+            args: _,
+        } => {
+            write!(&mut suffix, "{}", intrinsic.lower_case_name())?
+        }
         Node::Read {
             collect: _,
             indices,
diff --git a/hercules_ir/src/ir.rs b/hercules_ir/src/ir.rs
index 688902b6..7a126672 100644
--- a/hercules_ir/src/ir.rs
+++ b/hercules_ir/src/ir.rs
@@ -209,6 +209,10 @@ pub enum Node {
         dynamic_constants: Box<[DynamicConstantID]>,
         args: Box<[NodeID]>,
     },
+    IntrinsicCall {
+        intrinsic: Intrinsic,
+        args: Box<[NodeID]>,
+    },
     Read {
         collect: NodeID,
         indices: Box<[Index]>,
@@ -256,6 +260,40 @@ pub enum TernaryOperator {
     Select,
 }
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Intrinsic {
+    Abs,
+    ACos,
+    ACosh,
+    ASin,
+    ASinh,
+    ATan,
+    ATan2,
+    ATanh,
+    Cbrt,
+    Ceil,
+    Cos,
+    Cosh,
+    Exp,
+    Exp2,
+    ExpM1,
+    Floor,
+    Ln,
+    Ln1P,
+    Log,
+    Log10,
+    Log2,
+    Pow,
+    Powf,
+    Powi,
+    Round,
+    Sin,
+    Sinh,
+    Sqrt,
+    Tan,
+    Tanh,
+}
+
 impl Module {
     /*
      * Printing out types, constants, and dynamic constants fully requires a
@@ -1061,7 +1099,11 @@ impl Node {
                 function: _,
                 dynamic_constants: _,
                 args: _,
-            } => "Unary",
+            } => "Call",
+            Node::IntrinsicCall {
+                intrinsic: _,
+                args: _,
+            } => "Intrinsic",
             Node::Read {
                 collect: _,
                 indices: _,
@@ -1126,6 +1168,10 @@ impl Node {
                 dynamic_constants: _,
                 args: _,
             } => "call",
+            Node::IntrinsicCall {
+                intrinsic: _,
+                args: _,
+            } => "intrinsic",
             Node::Read {
                 collect: _,
                 indices: _,
@@ -1230,6 +1276,114 @@ impl TernaryOperator {
     }
 }
 
+impl Intrinsic {
+    pub fn parse<'a>(name: &'a str) -> Option<Self> {
+        match name {
+            "abs"    => Some(Intrinsic::Abs),
+            "acos"   => Some(Intrinsic::ACos),
+            "acosh"  => Some(Intrinsic::ACosh),
+            "asin"   => Some(Intrinsic::ASin),
+            "asinh"  => Some(Intrinsic::ASinh),
+            "atan"   => Some(Intrinsic::ATan),
+            "atan2"  => Some(Intrinsic::ATan2),
+            "atanh"  => Some(Intrinsic::ATanh),
+            "cbrt"   => Some(Intrinsic::Cbrt),
+            "ceil"   => Some(Intrinsic::Ceil),
+            "cos"    => Some(Intrinsic::Cos),
+            "cosh"   => Some(Intrinsic::Cosh),
+            "exp"    => Some(Intrinsic::Exp),
+            "exp2"   => Some(Intrinsic::Exp2),
+            "exp_m1" => Some(Intrinsic::ExpM1),
+            "floor"  => Some(Intrinsic::Floor),
+            "ln"     => Some(Intrinsic::Ln),
+            "ln_1p"  => Some(Intrinsic::Ln1P),
+            "log"    => Some(Intrinsic::Log),
+            "log10"  => Some(Intrinsic::Log10),
+            "log2"   => Some(Intrinsic::Log2),
+            "pow"    => Some(Intrinsic::Pow),
+            "powf"   => Some(Intrinsic::Powf),
+            "powi"   => Some(Intrinsic::Powi),
+            "round"  => Some(Intrinsic::Round),
+            "sin"    => Some(Intrinsic::Sin),
+            "sinh"   => Some(Intrinsic::Sinh),
+            "sqrt"   => Some(Intrinsic::Sqrt),
+            "tan"    => Some(Intrinsic::Tan),
+            "tanh"   => Some(Intrinsic::Tanh),
+            _ => None,
+        }
+    }
+
+    pub fn upper_case_name(&self) -> &'static str {
+        match self {
+            Intrinsic::Abs   => "Abs",
+            Intrinsic::ACos  => "Acos",
+            Intrinsic::ACosh => "Acosh",
+            Intrinsic::ASin  => "Asin",
+            Intrinsic::ASinh => "Asinh",
+            Intrinsic::ATan  => "Atan",
+            Intrinsic::ATan2 => "Atan2",
+            Intrinsic::ATanh => "Atanh",
+            Intrinsic::Cbrt  => "Cbrt",
+            Intrinsic::Ceil  => "Ceil",
+            Intrinsic::Cos   => "Cos",
+            Intrinsic::Cosh  => "Cosh",
+            Intrinsic::Exp   => "Exp",
+            Intrinsic::Exp2  => "Exp2",
+            Intrinsic::ExpM1 => "Exp_m1",
+            Intrinsic::Floor => "Floor",
+            Intrinsic::Ln    => "Ln",
+            Intrinsic::Ln1P  => "Ln_1p",
+            Intrinsic::Log   => "Log",
+            Intrinsic::Log10 => "Log10",
+            Intrinsic::Log2  => "Log2",
+            Intrinsic::Pow   => "Pow",
+            Intrinsic::Powf  => "Powf",
+            Intrinsic::Powi  => "Powi",
+            Intrinsic::Round => "Round",
+            Intrinsic::Sin   => "Sin",
+            Intrinsic::Sinh  => "Sinh",
+            Intrinsic::Sqrt  => "Sqrt",
+            Intrinsic::Tan   => "Tan",
+            Intrinsic::Tanh  => "Tanh",
+        }
+    }
+
+    pub fn lower_case_name(&self) -> &'static str {
+        match self {
+            Intrinsic::Abs   => "abs",
+            Intrinsic::ACos  => "acos",
+            Intrinsic::ACosh => "acosh",
+            Intrinsic::ASin  => "asin",
+            Intrinsic::ASinh => "asinh",
+            Intrinsic::ATan  => "atan",
+            Intrinsic::ATan2 => "atan2",
+            Intrinsic::ATanh => "atanh",
+            Intrinsic::Cbrt  => "cbrt",
+            Intrinsic::Ceil  => "ceil",
+            Intrinsic::Cos   => "cos",
+            Intrinsic::Cosh  => "cosh",
+            Intrinsic::Exp   => "exp",
+            Intrinsic::Exp2  => "exp2",
+            Intrinsic::ExpM1 => "exp_m1",
+            Intrinsic::Floor => "floor",
+            Intrinsic::Ln    => "ln",
+            Intrinsic::Ln1P  => "ln_1p",
+            Intrinsic::Log   => "log",
+            Intrinsic::Log10 => "log10",
+            Intrinsic::Log2  => "log2",
+            Intrinsic::Pow   => "pow",
+            Intrinsic::Powf  => "powf",
+            Intrinsic::Powi  => "powi",
+            Intrinsic::Round => "round",
+            Intrinsic::Sin   => "sin",
+            Intrinsic::Sinh  => "sinh",
+            Intrinsic::Sqrt  => "sqrt",
+            Intrinsic::Tan   => "tan",
+            Intrinsic::Tanh  => "tanh",
+        }
+    }
+}
+
 /*
  * Rust things to make newtyped IDs usable.
  */
diff --git a/hercules_ir/src/parse.rs b/hercules_ir/src/parse.rs
index 70eb270c..d53ffdb2 100644
--- a/hercules_ir/src/parse.rs
+++ b/hercules_ir/src/parse.rs
@@ -322,6 +322,7 @@ fn parse_node<'a>(
         "rsh" => parse_binary(ir_text, context, BinaryOperator::RSh)?,
         "select" => parse_ternary(ir_text, context, TernaryOperator::Select)?,
         "call" => parse_call(ir_text, context)?,
+        "intrinsic" => parse_intrinsic(ir_text, context)?,
         "read" => parse_read(ir_text, context)?,
         "write" => parse_write(ir_text, context)?,
         _ => Err(nom::Err::Error(nom::error::Error {
@@ -571,6 +572,41 @@ fn parse_call<'a>(ir_text: &'a str, context: &RefCell<Context<'a>>) -> nom::IRes
     ))
 }
 
+fn parse_intrinsic<'a>(ir_text: &'a str, context: &RefCell<Context<'a>>) -> nom::IResult<&'a str, Node> {
+    // Intrinsic nodes take an intrinsic name as an argument and a variable
+    // number of normal arguments.
+    let ir_text = nom::character::complete::multispace0(ir_text)?.0;
+    let ir_text = nom::character::complete::char('(')(ir_text)?.0;
+    let ir_text = nom::character::complete::multispace0(ir_text)?.0;
+    let (ir_text, mut intrinsic_and_args) = nom::multi::separated_list1(
+        nom::sequence::tuple((
+            nom::character::complete::multispace0,
+            nom::character::complete::char(','),
+            nom::character::complete::multispace0,
+        )),
+        parse_identifier,
+    )(ir_text)?;
+    let intrinsic = intrinsic_and_args.remove(0);
+    let args: Vec<NodeID> = intrinsic_and_args
+        .into_iter()
+        .map(|x| context.borrow_mut().get_node_id(x))
+        .collect();
+    let ir_text = nom::character::complete::multispace0(ir_text)?.0;
+    let ir_text = nom::character::complete::char(')')(ir_text)?.0;
+    let intrinsic = Intrinsic::parse(intrinsic)
+        .ok_or(nom::Err::Error(nom::error::Error {
+            input: ir_text,
+            code: nom::error::ErrorKind::IsNot,
+        }))?;
+    Ok((
+        ir_text,
+        Node::IntrinsicCall {
+            intrinsic,
+            args: args.into_boxed_slice(),
+        },
+    ))
+}
+
 fn parse_index<'a>(
     ir_text: &'a str,
     context: &RefCell<Context<'a>>,
diff --git a/hercules_ir/src/typecheck.rs b/hercules_ir/src/typecheck.rs
index 140edfe0..4b22f153 100644
--- a/hercules_ir/src/typecheck.rs
+++ b/hercules_ir/src/typecheck.rs
@@ -779,6 +779,135 @@ fn typeflow(
 
             Concrete(callee.return_type)
         }
+        Node::IntrinsicCall {
+            intrinsic,
+            args: _,
+        } => {
+            let num_params =
+                match intrinsic {
+                    Intrinsic::Abs     | Intrinsic::ACos  | Intrinsic::ACosh
+                    | Intrinsic::ASin  | Intrinsic::ASinh | Intrinsic::ATan
+                    | Intrinsic::ATanh | Intrinsic::Cbrt  | Intrinsic::Ceil
+                    | Intrinsic::Cos   | Intrinsic::Cosh  | Intrinsic::Exp
+                    | Intrinsic::Exp2  | Intrinsic::ExpM1 | Intrinsic::Floor
+                    | Intrinsic::Ln    | Intrinsic::Ln1P  | Intrinsic::Log10
+                    | Intrinsic::Log2  | Intrinsic::Round | Intrinsic::Sin
+                    | Intrinsic::Sinh  | Intrinsic::Sqrt  | Intrinsic::Tan
+                    | Intrinsic::Tanh
+                        => 1,
+                    Intrinsic::ATan2   | Intrinsic::Log   | Intrinsic::Pow
+                    | Intrinsic::Powf  | Intrinsic::Powi
+                        => 2,
+                };
+
+            // Check number of run-time arguments
+            if inputs.len() != num_params {
+                return Error(format!(
+                    "Intrinsic {} has {} inputs, but calls an intrinsic with {} parameters.",
+                    intrinsic.lower_case_name(),
+                    inputs.len(),
+                    num_params,
+                ));
+            }
+
+            // Check argument types. This process depends on the intrinsic
+            // since intrinsics can be polymorphic
+            // We also return the return type from here
+            match intrinsic {
+                // Intrinsics that take any numeric type and return the same
+                Intrinsic::Abs => {
+                    if let Concrete(id) = inputs[0] {
+                        if types[id.idx()].is_arithmetic() {
+                            Concrete(*id)
+                        } else {
+                            Error(format!("{} intrinsic cannot have non-numeric input type.",
+                                          intrinsic.lower_case_name()))
+                        }
+                    } else {
+                        // Otherwise, propogate errors and unconstrained types
+                        (*inputs[0]).clone()
+                    }
+                },
+                // Intrinsics that take any float type and return the same
+                Intrinsic::ACos    | Intrinsic::ACosh | Intrinsic::ASin
+                | Intrinsic::ASinh | Intrinsic::ATan  | Intrinsic::ATanh
+                | Intrinsic::Cbrt  | Intrinsic::Ceil  | Intrinsic::Cos
+                | Intrinsic::Cosh  | Intrinsic::Exp   | Intrinsic::Exp2
+                | Intrinsic::ExpM1 | Intrinsic::Floor | Intrinsic::Ln
+                | Intrinsic::Ln1P  | Intrinsic::Log10 | Intrinsic::Log2 
+                | Intrinsic::Round | Intrinsic::Sin   | Intrinsic::Sinh
+                | Intrinsic::Sqrt  | Intrinsic::Tan   | Intrinsic::Tanh
+                => {
+                    if let Concrete(id) = inputs[0] {
+                        if types[id.idx()].is_float() {
+                            Concrete(*id)
+                        } else {
+                            Error(format!("{} intrinsic cannot have non-float input type.",
+                                          intrinsic.lower_case_name()))
+                        }
+                    } else {
+                        // Otherwise, propogate errors and unconstrained types
+                        (*inputs[0]).clone()
+                    }
+                },
+                // Intrinsics that take any two values of the same float type
+                // and return the same
+                Intrinsic::ATan2   | Intrinsic::Log   | Intrinsic::Powf
+                => {
+                    let input_ty = TypeSemilattice::meet(inputs[0], inputs[1]);
+
+                    if let Concrete(id) = input_ty {
+                        if types[id.idx()].is_float() {
+                            Concrete(id)
+                        } else {
+                            Error(format!("{} intrinsic cannot have non-float input types.",
+                                          intrinsic.lower_case_name()))
+                        }
+                    } else {
+                        // Otherwise, propogate errors and unconstrained types
+                        (*inputs[0]).clone()
+                    }
+                },
+                Intrinsic::Pow => {
+                    if let Concrete(id) = inputs[0] {
+                        if types[id.idx()].is_fixed() {
+                            if let Concrete(id) = inputs[1] {
+                                if types[id.idx()] != Type::UnsignedInteger32 {
+                                    return Error(format!("{} intrinsic expects u32 as second argument.",
+                                                         intrinsic.lower_case_name()));
+                                }
+                            }
+                            Concrete(*id)
+                        } else {
+                            Error(format!("{} intrinsic cannot have non-integer first argument.",
+                                          intrinsic.lower_case_name()))
+                        }
+                    } else {
+                        // Otherwise, propagate errors and unconstrained types
+                        (*inputs[0]).clone()
+                    }
+                },
+                Intrinsic::Powi => {
+                    if let Concrete(id) = inputs[0] {
+                        if types[id.idx()].is_float() {
+                            if let Concrete(id) = inputs[1] {
+                                if types[id.idx()] != Type::Integer32 {
+                                    return Error(format!("{} intrinsic expects i32 as second argument.",
+                                                         intrinsic.lower_case_name()));
+                                }
+                            }
+                            Concrete(*id)
+                        } else {
+                            Error(format!("{} intrinsic cannot have non-float first argument.",
+                                          intrinsic.lower_case_name()))
+                        }
+                    } else {
+                        // Otherwise, propagate errors and unconstrained types
+                        (*inputs[0]).clone()
+                    }
+                },
+            }
+        },
         Node::Read {
             collect: _,
             indices,
diff --git a/hercules_opt/src/ccp.rs b/hercules_opt/src/ccp.rs
index a3506948..6f644d46 100644
--- a/hercules_opt/src/ccp.rs
+++ b/hercules_opt/src/ccp.rs
@@ -128,6 +128,35 @@ impl Semilattice for ConstantLattice {
     }
 }
 
+/* Macros used for constant propagation with intrinsic functions */
+macro_rules! unary_float_intrinsic {
+    ($intrinsic : expr, $constants : expr, $func : ident) => {
+        if let Constant::Float32(v) = $constants[0] {
+            ConstantLattice::Constant(Constant::Float32(ordered_float::OrderedFloat(v.$func())))
+        } else if let Constant::Float64(v) = $constants[0] {
+            ConstantLattice::Constant(Constant::Float64(ordered_float::OrderedFloat(v.$func())))
+        } else {
+            panic!("Unsupported combination of intrinsic {} and constant value. Did typechecking succeed?",
+                   $intrinsic.lower_case_name())
+        }
+    }
+}
+
+macro_rules! binary_float_intrinsic {
+    ($intrinsic : expr, $constants : expr, $func : ident) => {
+        if let (Constant::Float32(x), Constant::Float32(y))
+                    = ($constants[0], $constants[1]) {
+            ConstantLattice::Constant(Constant::Float32(ordered_float::OrderedFloat(x.$func(**y))))
+        } else if let (Constant::Float64(x), Constant::Float64(y))
+                    = ($constants[0], $constants[1]) {
+            ConstantLattice::Constant(Constant::Float64(ordered_float::OrderedFloat(x.$func(**y))))
+        } else {
+            panic!("Unsupported combination of intrinsic {} and constant values. Did typechecking succeed?",
+                   $intrinsic.lower_case_name())
+        }
+    }
+}
+
 /*
  * Top level function to run conditional constant propagation.
  */
@@ -705,6 +734,132 @@ fn ccp_flow_function(
             }),
             constant: ConstantLattice::bottom(),
         },
+        Node::IntrinsicCall {
+            intrinsic,
+            args,
+        } => {
+            let mut new_reachability = ReachabilityLattice::top();
+            let mut new_constant = ConstantLattice::top();
+            let mut constants = vec![];
+            let mut all_constants = true;
+
+            for arg in args.iter() {
+                let CCPLattice { ref reachability, ref constant }
+                    = inputs[arg.idx()];
+
+                new_reachability = 
+                    ReachabilityLattice::meet(&new_reachability, reachability);
+                new_constant =
+                    ConstantLattice::meet(&new_constant, constant);
+
+                if let ConstantLattice::Constant(constant) = constant {
+                    constants.push(constant);
+                } else {
+                    all_constants = false;
+                }
+            }
+
+            if all_constants {
+                new_constant =
+                    match intrinsic {
+                        Intrinsic::Abs => {
+                            if let Constant::Integer8(i) = constants[0] {
+                                ConstantLattice::Constant(Constant::Integer8(i.abs()))
+                            } else if let Constant::Integer16(i) = constants[0] {
+                                ConstantLattice::Constant(Constant::Integer16(i.abs()))
+                            } else if let Constant::Integer32(i) = constants[0] {
+                                ConstantLattice::Constant(Constant::Integer32(i.abs()))
+                            } else if let Constant::Integer64(i) = constants[0] {
+                                ConstantLattice::Constant(Constant::Integer64(i.abs()))
+                            } else if let Constant::UnsignedInteger8(i) = constants[0] {
+                                ConstantLattice::Constant(Constant::UnsignedInteger8(*i))
+                            } else if let Constant::UnsignedInteger16(i) = constants[0] {
+                                ConstantLattice::Constant(Constant::UnsignedInteger16(*i))
+                            } else if let Constant::UnsignedInteger32(i) = constants[0] {
+                                ConstantLattice::Constant(Constant::UnsignedInteger32(*i))
+                            } else if let Constant::UnsignedInteger64(i) = constants[0] {
+                                ConstantLattice::Constant(Constant::UnsignedInteger64(*i))
+                            } else if let Constant::Float32(i) = constants[0] {
+                                ConstantLattice::Constant(Constant::Float32(ordered_float::OrderedFloat(i.abs())))
+                            } else if let Constant::Float64(i) = constants[0] {
+                                ConstantLattice::Constant(Constant::Float64(ordered_float::OrderedFloat(i.abs())))
+                            } else {
+                                panic!("Unsupported combination of intrinsic abs and constant value. Did typechecking succeed?")
+                            }
+                        },
+                        Intrinsic::ACos => unary_float_intrinsic!(intrinsic, constants, acos),
+                        Intrinsic::ACosh => unary_float_intrinsic!(intrinsic, constants, acosh),
+                        Intrinsic::ASin => unary_float_intrinsic!(intrinsic, constants, asin),
+                        Intrinsic::ASinh => unary_float_intrinsic!(intrinsic, constants, asinh),
+                        Intrinsic::ATan => unary_float_intrinsic!(intrinsic, constants, atan),
+                        Intrinsic::ATan2 => binary_float_intrinsic!(intrinsic, constants, atan2),
+                        Intrinsic::ATanh => unary_float_intrinsic!(intrinsic, constants, atanh),
+                        Intrinsic::Cbrt => unary_float_intrinsic!(intrinsic, constants, cbrt),
+                        Intrinsic::Ceil => unary_float_intrinsic!(intrinsic, constants, ceil),
+                        Intrinsic::Cos => unary_float_intrinsic!(intrinsic, constants, cos),
+                        Intrinsic::Cosh => unary_float_intrinsic!(intrinsic, constants, cosh),
+                        Intrinsic::Exp => unary_float_intrinsic!(intrinsic, constants, exp),
+                        Intrinsic::Exp2 => unary_float_intrinsic!(intrinsic, constants, exp2),
+                        Intrinsic::ExpM1 => unary_float_intrinsic!(intrinsic, constants, exp_m1),
+                        Intrinsic::Floor => unary_float_intrinsic!(intrinsic, constants, floor),
+                        Intrinsic::Ln => unary_float_intrinsic!(intrinsic, constants, ln),
+                        Intrinsic::Ln1P => unary_float_intrinsic!(intrinsic, constants, ln_1p),
+                        Intrinsic::Log => binary_float_intrinsic!(intrinsic, constants, log),
+                        Intrinsic::Log10 => unary_float_intrinsic!(intrinsic, constants, log10),
+                        Intrinsic::Log2 => unary_float_intrinsic!(intrinsic, constants, log2),
+                        Intrinsic::Pow => {
+                            if let Constant::UnsignedInteger32(p) = constants[1] {
+                                if let Constant::Integer8(i) = constants[0] {
+                                    ConstantLattice::Constant(Constant::Integer8(i.pow(*p)))
+                                } else if let Constant::Integer16(i) = constants[0] {
+                                    ConstantLattice::Constant(Constant::Integer16(i.pow(*p)))
+                                } else if let Constant::Integer32(i) = constants[0] {
+                                    ConstantLattice::Constant(Constant::Integer32(i.pow(*p)))
+                                } else if let Constant::Integer64(i) = constants[0] {
+                                    ConstantLattice::Constant(Constant::Integer64(i.pow(*p)))
+                                } else if let Constant::UnsignedInteger8(i) = constants[0] {
+                                    ConstantLattice::Constant(Constant::UnsignedInteger8(i.pow(*p)))
+                                } else if let Constant::UnsignedInteger16(i) = constants[0] {
+                                    ConstantLattice::Constant(Constant::UnsignedInteger16(i.pow(*p)))
+                                } else if let Constant::UnsignedInteger32(i) = constants[0] {
+                                    ConstantLattice::Constant(Constant::UnsignedInteger32(i.pow(*p)))
+                                } else if let Constant::UnsignedInteger64(i) = constants[0] {
+                                    ConstantLattice::Constant(Constant::UnsignedInteger64(i.pow(*p)))
+                                } else {
+                                    panic!("Unsupported combination of intrinsic pow and constant values. Did typechecking succeed?")
+                                }
+                            } else {
+                                panic!("Unsupported combination of intrinsic pow and constant values. Did typechecking succeed?")
+                            }
+                        },
+                        Intrinsic::Powf => binary_float_intrinsic!(intrinsic, constants, powf),
+                        Intrinsic::Powi => {
+                            if let Constant::Integer32(p) = constants[1] {
+                                if let Constant::Float32(v) = constants[0] {
+                                    ConstantLattice::Constant(Constant::Float32(ordered_float::OrderedFloat(v.powi(*p))))
+                                } else if let Constant::Float64(v) = constants[0] {
+                                    ConstantLattice::Constant(Constant::Float64(ordered_float::OrderedFloat(v.powi(*p))))
+                                } else {
+                                    panic!("Unsupported combination of intrinsic powi and constant value. Did typechecking succeed?")
+                                }
+                            } else {
+                                panic!("Unsupported combination of intrinsic powi and constant values. Did typechecking succeed?")
+                            }
+                        },
+                        Intrinsic::Round => unary_float_intrinsic!(intrinsic, constants, round),
+                        Intrinsic::Sin => unary_float_intrinsic!(intrinsic, constants, sin),
+                        Intrinsic::Sinh => unary_float_intrinsic!(intrinsic, constants, sinh),
+                        Intrinsic::Sqrt => unary_float_intrinsic!(intrinsic, constants, sqrt),
+                        Intrinsic::Tan => unary_float_intrinsic!(intrinsic, constants, tan),
+                        Intrinsic::Tanh => unary_float_intrinsic!(intrinsic, constants, tanh),
+                    };
+            }
+
+            CCPLattice {
+                reachability: new_reachability,
+                constant: new_constant,
+            }
+        },
         Node::Read { collect, indices: _ } => {
             CCPLattice {
                 reachability: inputs[collect.idx()].reachability.clone(),
-- 
GitLab