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