1use crate::Diagnostic;
4use pliron::{
5 common_traits::Verify,
6 context::{Context, Ptr},
7 derive::pliron_op,
8 op::Op,
9 operation::Operation,
10 printable::Printable,
11 result::Result,
12 r#type::{TypeHandle, Typed},
13 value::Value,
14};
15use pliron_arith::{
16 attributes::{FastMathFlags, FastMathFlagsAttr},
17 shaped::{self, Shaped},
18};
19
20pliron::dict_key!(ATTR_KEY_MATH_FASTMATH, "math_fastmath");
21
22fn fail(ctx: &Context, op: Ptr<Operation>, reason: &'static str) -> pliron::result::Error {
24 let location = op.deref(ctx).loc().disp(ctx).to_string();
25 Diagnostic::InvalidOperation {
26 operation: Operation::get_op_dyn(op, ctx).get_opid().to_string(),
27 location,
28 reason,
29 }
30 .into()
31}
32
33fn verify_structure(ctx: &Context, op: Ptr<Operation>, operands: usize) -> Result<()> {
35 let (num_operands, num_results, num_regions, num_successors) = {
36 let ir = op.deref(ctx);
37 (
38 ir.get_num_operands(),
39 ir.get_num_results(),
40 ir.num_regions(),
41 ir.get_num_successors(),
42 )
43 };
44 if num_operands != operands || num_results != 1 {
45 return Err(fail(ctx, op, "operand or result count mismatch"));
46 }
47 if num_regions != 0 || num_successors != 0 {
48 return Err(fail(
49 ctx,
50 op,
51 "operation must not have regions or successors",
52 ));
53 }
54 Ok(())
55}
56
57fn operand_type(ctx: &Context, op: Ptr<Operation>, index: usize) -> TypeHandle {
58 op.deref(ctx).get_operand(index).get_type(ctx)
59}
60
61fn result_type(ctx: &Context, op: Ptr<Operation>) -> TypeHandle {
62 op.deref(ctx).get_result(0).get_type(ctx)
63}
64
65fn classify(ctx: &Context, op: Ptr<Operation>, ty: TypeHandle) -> Result<Shaped> {
66 shaped::classify(ctx, ty).ok_or_else(|| {
67 fail(
68 ctx,
69 op,
70 "type must be a scalar or a tensor or vector of scalars",
71 )
72 })
73}
74
75#[derive(Clone, Copy)]
77enum Domain {
78 Float,
80 Integer,
82}
83
84fn in_domain(ctx: &Context, element: TypeHandle, domain: Domain) -> bool {
85 match domain {
86 Domain::Float => shaped::is_float(ctx, element),
87 Domain::Integer => shaped::signless_width(ctx, element).is_some(),
88 }
89}
90
91fn verify_same_type(ctx: &Context, op: Ptr<Operation>, arity: usize, domain: Domain) -> Result<()> {
93 verify_structure(ctx, op, arity)?;
94 let result = result_type(ctx, op);
95 if (0..arity).any(|i| operand_type(ctx, op, i) != result) {
96 return Err(fail(ctx, op, "operands and result must have the same type"));
97 }
98 let shaped = classify(ctx, op, result)?;
99 if !in_domain(ctx, shaped.element, domain) {
100 return Err(fail(
101 ctx,
102 op,
103 "element type is outside the operation's domain",
104 ));
105 }
106 Ok(())
107}
108
109fn new_op<T: Op>(
110 ctx: &mut Context,
111 make: fn(Ptr<Operation>) -> T,
112 result: TypeHandle,
113 operands: Vec<Value>,
114) -> T {
115 let info = T::get_concrete_op_info();
116 make(Operation::new(ctx, info, vec![result], operands, vec![], 0))
117}
118
119macro_rules! fastmath_accessors {
120 ($struct:ident) => {
121 pliron_arith::shared_attr!(
122 $struct,
123 ATTR_KEY_MATH_FASTMATH,
124 FastMathFlagsAttr,
125 get_attr_math_fastmath,
126 set_attr_math_fastmath
127 );
128 impl $struct {
129 pub fn fastmath(&self, ctx: &Context) -> FastMathFlags {
131 self.get_attr_math_fastmath(ctx)
132 .map(|attr| attr.value)
133 .unwrap_or_default()
134 }
135 pub fn set_fastmath(&self, ctx: &Context, flags: FastMathFlags) {
137 self.set_attr_math_fastmath(ctx, FastMathFlagsAttr::new(flags));
138 }
139 }
140 };
141}
142
143macro_rules! result_accessor {
144 ($struct:ident) => {
145 impl $struct {
146 pub fn result(&self, ctx: &Context) -> Value {
148 self.get_operation().deref(ctx).get_result(0)
149 }
150 }
151 };
152}
153
154macro_rules! unary_op {
155 ($struct:ident, $name:literal, $doc:literal, Float) => {
156 #[doc = $doc]
157 #[pliron_op(
160 name = $name,
161 format = "$0 attr_dict `:` typesig",
162 interfaces = [pliron::builtin::op_interfaces::OneResultInterface]
163 )]
164 pub struct $struct;
165 unary_op!(@common $struct, Domain::Float);
166 fastmath_accessors!($struct);
167 };
168 ($struct:ident, $name:literal, $doc:literal, Integer) => {
169 #[doc = $doc]
170 #[pliron_op(
173 name = $name,
174 format = "$0 attr_dict `:` typesig",
175 interfaces = [pliron::builtin::op_interfaces::OneResultInterface]
176 )]
177 pub struct $struct;
178 unary_op!(@common $struct, Domain::Integer);
179 };
180 (@common $struct:ident, $domain:expr) => {
181 impl $struct {
182 pub fn new(ctx: &mut Context, operand: Value) -> Self {
184 let ty = operand.get_type(ctx);
185 new_op(ctx, |op| Self { op }, ty, vec![operand])
186 }
187 pub fn operand(&self, ctx: &Context) -> Value {
189 self.get_operation().deref(ctx).get_operand(0)
190 }
191 }
192 result_accessor!($struct);
193 impl Verify for $struct {
194 fn verify(&self, ctx: &Context) -> Result<()> {
195 verify_same_type(ctx, self.get_operation(), 1, $domain)
196 }
197 }
198 };
199}
200
201macro_rules! binary_op {
202 ($struct:ident, $name:literal, $doc:literal, Float) => {
203 #[doc = $doc]
204 #[pliron_op(
207 name = $name,
208 format = "$0 `,` $1 attr_dict `:` typesig",
209 interfaces = [pliron::builtin::op_interfaces::OneResultInterface]
210 )]
211 pub struct $struct;
212 binary_op!(@common $struct, Domain::Float);
213 fastmath_accessors!($struct);
214 };
215 ($struct:ident, $name:literal, $doc:literal, Integer) => {
216 #[doc = $doc]
217 #[pliron_op(
220 name = $name,
221 format = "$0 `,` $1 attr_dict `:` typesig",
222 interfaces = [pliron::builtin::op_interfaces::OneResultInterface]
223 )]
224 pub struct $struct;
225 binary_op!(@common $struct, Domain::Integer);
226 };
227 (@common $struct:ident, $domain:expr) => {
228 impl $struct {
229 pub fn new(ctx: &mut Context, lhs: Value, rhs: Value) -> Self {
231 let ty = lhs.get_type(ctx);
232 new_op(ctx, |op| Self { op }, ty, vec![lhs, rhs])
233 }
234 pub fn lhs(&self, ctx: &Context) -> Value {
236 self.get_operation().deref(ctx).get_operand(0)
237 }
238 pub fn rhs(&self, ctx: &Context) -> Value {
240 self.get_operation().deref(ctx).get_operand(1)
241 }
242 }
243 result_accessor!($struct);
244 impl Verify for $struct {
245 fn verify(&self, ctx: &Context) -> Result<()> {
246 verify_same_type(ctx, self.get_operation(), 2, $domain)
247 }
248 }
249 };
250}
251
252macro_rules! classification_op {
253 ($struct:ident, $name:literal, $doc:literal) => {
254 #[doc = $doc]
255 #[pliron_op(
258 name = $name,
259 format = "$0 attr_dict `:` typesig",
260 interfaces = [pliron::builtin::op_interfaces::OneResultInterface]
261 )]
262 pub struct $struct;
263 impl $struct {
264 pub fn new(ctx: &mut Context, operand: Value) -> Self {
268 let ty = operand.get_type(ctx);
269 let i1 = shaped::i1(ctx);
270 let result = shaped::classify(ctx, ty)
271 .map(|s| shaped::with_element(ctx, &s, i1))
272 .unwrap_or(i1);
273 new_op(ctx, |op| Self { op }, result, vec![operand])
274 }
275 pub fn operand(&self, ctx: &Context) -> Value {
277 self.get_operation().deref(ctx).get_operand(0)
278 }
279 }
280 result_accessor!($struct);
281 fastmath_accessors!($struct);
282 impl Verify for $struct {
283 fn verify(&self, ctx: &Context) -> Result<()> {
284 let op = self.get_operation();
285 verify_structure(ctx, op, 1)?;
286 let input = classify(ctx, op, operand_type(ctx, op, 0))?;
287 if !shaped::is_float(ctx, input.element) {
288 return Err(fail(ctx, op, "operand must be floating-point"));
289 }
290 let output = classify(ctx, op, result_type(ctx, op))?;
291 if !output.same_shape(&input) || output.element != shaped::i1(ctx) {
292 return Err(fail(ctx, op, "result must be i1 with the operand's shape"));
293 }
294 Ok(())
295 }
296 }
297 };
298}
299
300unary_op!(AbsFOp, "math.absf", "Floating-point absolute value.", Float);
301unary_op!(
302 CeilOp,
303 "math.ceil",
304 "Rounds toward positive infinity.",
305 Float
306);
307unary_op!(
308 FloorOp,
309 "math.floor",
310 "Rounds toward negative infinity.",
311 Float
312);
313unary_op!(
314 RoundOp,
315 "math.round",
316 "Rounds to nearest, ties away from zero.",
317 Float
318);
319unary_op!(
320 RoundEvenOp,
321 "math.roundeven",
322 "Rounds to nearest, ties to even.",
323 Float
324);
325unary_op!(TruncOp, "math.trunc", "Rounds toward zero.", Float);
326unary_op!(ExpOp, "math.exp", "Base-e exponential.", Float);
327unary_op!(Exp2Op, "math.exp2", "Base-2 exponential.", Float);
328unary_op!(
329 ExpM1Op,
330 "math.expm1",
331 "`exp(x) - 1`, accurate near zero.",
332 Float
333);
334unary_op!(LogOp, "math.log", "Natural logarithm.", Float);
335unary_op!(Log2Op, "math.log2", "Base-2 logarithm.", Float);
336unary_op!(Log10Op, "math.log10", "Base-10 logarithm.", Float);
337unary_op!(
338 Log1pOp,
339 "math.log1p",
340 "`log(1 + x)`, accurate near zero.",
341 Float
342);
343unary_op!(SqrtOp, "math.sqrt", "Square root.", Float);
344unary_op!(RsqrtOp, "math.rsqrt", "Reciprocal square root.", Float);
345unary_op!(CbrtOp, "math.cbrt", "Cube root.", Float);
346unary_op!(SinOp, "math.sin", "Sine.", Float);
347unary_op!(CosOp, "math.cos", "Cosine.", Float);
348unary_op!(TanOp, "math.tan", "Tangent.", Float);
349unary_op!(AsinOp, "math.asin", "Arcsine.", Float);
350unary_op!(AcosOp, "math.acos", "Arccosine.", Float);
351unary_op!(AtanOp, "math.atan", "Arctangent.", Float);
352unary_op!(SinhOp, "math.sinh", "Hyperbolic sine.", Float);
353unary_op!(CoshOp, "math.cosh", "Hyperbolic cosine.", Float);
354unary_op!(TanhOp, "math.tanh", "Hyperbolic tangent.", Float);
355unary_op!(AsinhOp, "math.asinh", "Inverse hyperbolic sine.", Float);
356unary_op!(AcoshOp, "math.acosh", "Inverse hyperbolic cosine.", Float);
357unary_op!(AtanhOp, "math.atanh", "Inverse hyperbolic tangent.", Float);
358unary_op!(ErfOp, "math.erf", "Error function.", Float);
359unary_op!(ErfcOp, "math.erfc", "Complementary error function.", Float);
360
361binary_op!(
362 Atan2Op,
363 "math.atan2",
364 "Two-argument arctangent of `lhs / rhs`.",
365 Float
366);
367binary_op!(PowFOp, "math.powf", "Floating-point power.", Float);
368binary_op!(
369 CopySignOp,
370 "math.copysign",
371 "Magnitude of `lhs` with the sign of `rhs`.",
372 Float
373);
374
375unary_op!(
376 AbsIOp,
377 "math.absi",
378 "Integer absolute value; the minimum value wraps.",
379 Integer
380);
381unary_op!(
382 CountLeadingZerosOp,
383 "math.ctlz",
384 "Counts leading zero bits.",
385 Integer
386);
387unary_op!(
388 CountTrailingZerosOp,
389 "math.cttz",
390 "Counts trailing zero bits.",
391 Integer
392);
393unary_op!(CtPopOp, "math.ctpop", "Counts set bits.", Integer);
394binary_op!(
395 IPowIOp,
396 "math.ipowi",
397 "Signed integer power; negative exponents follow MLIR semantics.",
398 Integer
399);
400
401classification_op!(IsNaNOp, "math.isnan", "Tests for NaN.");
402classification_op!(
403 IsInfOp,
404 "math.isinf",
405 "Tests for positive or negative infinity."
406);
407classification_op!(IsFiniteOp, "math.isfinite", "Tests for a finite value.");
408classification_op!(
409 IsNormalOp,
410 "math.isnormal",
411 "Tests for a normal (not subnormal) value."
412);
413
414#[pliron_op(
418 name = "math.fma",
419 format = "$0 `,` $1 `,` $2 attr_dict `:` typesig",
420 interfaces = [pliron::builtin::op_interfaces::OneResultInterface]
421)]
422pub struct FmaOp;
423
424impl FmaOp {
425 pub fn new(ctx: &mut Context, a: Value, b: Value, c: Value) -> Self {
427 let ty = a.get_type(ctx);
428 new_op(ctx, |op| Self { op }, ty, vec![a, b, c])
429 }
430 pub fn operand(&self, ctx: &Context, index: usize) -> Value {
432 self.get_operation().deref(ctx).get_operand(index)
433 }
434}
435result_accessor!(FmaOp);
436fastmath_accessors!(FmaOp);
437
438impl Verify for FmaOp {
439 fn verify(&self, ctx: &Context) -> Result<()> {
440 verify_same_type(ctx, self.get_operation(), 3, Domain::Float)
441 }
442}
443
444#[pliron_op(
448 name = "math.clampf",
449 format = "$0 `,` $1 `,` $2 attr_dict `:` typesig",
450 interfaces = [pliron::builtin::op_interfaces::OneResultInterface]
451)]
452pub struct ClampFOp;
453
454impl ClampFOp {
455 pub fn new(ctx: &mut Context, value: Value, min: Value, max: Value) -> Self {
457 let ty = value.get_type(ctx);
458 new_op(ctx, |op| Self { op }, ty, vec![value, min, max])
459 }
460 pub fn value(&self, ctx: &Context) -> Value {
462 self.get_operation().deref(ctx).get_operand(0)
463 }
464 pub fn min(&self, ctx: &Context) -> Value {
466 self.get_operation().deref(ctx).get_operand(1)
467 }
468 pub fn max(&self, ctx: &Context) -> Value {
470 self.get_operation().deref(ctx).get_operand(2)
471 }
472}
473result_accessor!(ClampFOp);
474fastmath_accessors!(ClampFOp);
475
476impl Verify for ClampFOp {
477 fn verify(&self, ctx: &Context) -> Result<()> {
478 verify_same_type(ctx, self.get_operation(), 3, Domain::Float)
479 }
480}
481
482#[pliron_op(
486 name = "math.sincos",
487 format = "$0 attr_dict `:` typesig",
488 interfaces = [pliron::builtin::op_interfaces::NResultsInterface<2>]
489)]
490pub struct SincosOp;
491
492impl SincosOp {
493 pub fn new(ctx: &mut Context, operand: Value) -> Self {
495 let ty = operand.get_type(ctx);
496 Self {
497 op: Operation::new(
498 ctx,
499 Self::get_concrete_op_info(),
500 vec![ty, ty],
501 vec![operand],
502 vec![],
503 0,
504 ),
505 }
506 }
507 pub fn operand(&self, ctx: &Context) -> Value {
509 self.get_operation().deref(ctx).get_operand(0)
510 }
511 pub fn sin(&self, ctx: &Context) -> Value {
513 self.get_operation().deref(ctx).get_result(0)
514 }
515 pub fn cos(&self, ctx: &Context) -> Value {
517 self.get_operation().deref(ctx).get_result(1)
518 }
519}
520fastmath_accessors!(SincosOp);
521
522impl Verify for SincosOp {
523 fn verify(&self, ctx: &Context) -> Result<()> {
524 let op = self.get_operation();
525 let (operands, results, regions, successors) = {
526 let ir = op.deref(ctx);
527 (
528 ir.get_num_operands(),
529 ir.get_num_results(),
530 ir.num_regions(),
531 ir.get_num_successors(),
532 )
533 };
534 if operands != 1 || results != 2 || regions != 0 || successors != 0 {
535 return Err(fail(
536 ctx,
537 op,
538 "expected one operand, two results, and no regions",
539 ));
540 }
541 let ty = operand_type(ctx, op, 0);
542 let ir = op.deref(ctx);
543 if ir.get_result(0).get_type(ctx) != ty || ir.get_result(1).get_type(ctx) != ty {
544 return Err(fail(ctx, op, "operand and results must have the same type"));
545 }
546 drop(ir);
547 let shaped = classify(ctx, op, ty)?;
548 if !in_domain(ctx, shaped.element, Domain::Float) {
549 return Err(fail(ctx, op, "element type must be floating point"));
550 }
551 Ok(())
552 }
553}
554
555#[pliron_op(
560 name = "math.fpowi",
561 format = "$0 `,` $1 attr_dict `:` typesig",
562 interfaces = [pliron::builtin::op_interfaces::OneResultInterface]
563)]
564pub struct FPowIOp;
565
566impl FPowIOp {
567 pub fn new(ctx: &mut Context, base: Value, exponent: Value) -> Self {
569 let ty = base.get_type(ctx);
570 new_op(ctx, |op| Self { op }, ty, vec![base, exponent])
571 }
572 pub fn base(&self, ctx: &Context) -> Value {
574 self.get_operation().deref(ctx).get_operand(0)
575 }
576 pub fn exponent(&self, ctx: &Context) -> Value {
578 self.get_operation().deref(ctx).get_operand(1)
579 }
580}
581result_accessor!(FPowIOp);
582fastmath_accessors!(FPowIOp);
583
584impl Verify for FPowIOp {
585 fn verify(&self, ctx: &Context) -> Result<()> {
586 let op = self.get_operation();
587 verify_structure(ctx, op, 2)?;
588 let result = result_type(ctx, op);
589 if operand_type(ctx, op, 0) != result {
590 return Err(fail(ctx, op, "base and result must have the same type"));
591 }
592 let base = classify(ctx, op, result)?;
593 if !shaped::is_float(ctx, base.element) {
594 return Err(fail(ctx, op, "base must be floating-point"));
595 }
596 let exponent = classify(ctx, op, operand_type(ctx, op, 1))?;
597 if !exponent.same_shape(&base) || shaped::signless_width(ctx, exponent.element).is_none() {
598 return Err(fail(
599 ctx,
600 op,
601 "exponent must be a signless integer with the base's shape",
602 ));
603 }
604 Ok(())
605 }
606}