IntrinsicFunction

An intrinsic function. An expr node.

Declaration

Syntax

IntrinsicFunction(expr* args, int intrinsic_id, int overload_id,
    ttype type, expr? value)

Arguments

  • args represents all arguments passed to the function

  • intrinsic_id is the unique ID of the generic intrinsic function

  • overload_id is the ID of the signature within the given generic function

  • type represents the type of the output

  • value is an optional compile time value

Return values

The return value is the expression that the IntrinsicFunction represents.

Description

IntrinsicFunction represents an intrinsic function (such as Abs, Modulo, Sin, Cos, LegendreP, FlipSign, …) that either the backend or the middle-end (optimizer) needs to have some special logic for. Typically a math function, but does not have to be.

IntrinsicFunction is both side-effect-free (no writes to global variables) and deterministic (no reads from global variables). They can be used inside parallel code and cached. There are two kinds:

  • elemental: the function is defined as a scalar function and it can be vectorized over any argument(s). Examples: Sin, Cos, LegendreP, Abs

  • non-elemental: it accepts arrays as arguments and the function cannot be defined as a scalar function. Examples: Sum, Any, MinLoc

The intrinsic_id determines the generic function uniquely (Sin and Abs have different number, but IntegerAbs and RealAbs share the number) and overload_id uniquely determines the signature starting from 0 for each generic function (e.g., IntegerAbs, RealAbs and ComplexAbs can have overload_id equal to 0, 1 and 2, and RealSin, ComplexSin can be 0, 1).

Backend use cases: Some architectures have special hardware instructions for operations like Sqrt or Sin and if they are faster than a software implementation, the backend will use it. This includes the FlipSign function which is our own “special function” that the optimizer emits for certain conditional floating point operations, and the backend emits an efficient bit manipulation implementation for architectures that support it.

Middle-end use cases: the middle-end can use the high level semantics to simplify, such as sin(e)**2 + cos(e)**2 -> 1, or it could approximate expressions like if (abs(sin(x) - 0.5) < 0.3) with a lower accuracy version of sin.

We provide ASR -> ASR lowering transformations that substitute the given intrinsic function with an ASR implementation using more primitive ASR nodes, typically implemented in the surface language (say a sin implementation using argument reduction and a polynomial fit, or a sqrt implementation using a general power formula x**(0.5), or LegendreP(2,x) implementation using a formula (3*x**2-1)/2).

This design also makes it possible to allow selecting using command line options how certain intrinsic functions should be implemented, for example if trigonometric functions should be implemented using our own fast implementation, libm accurate implementation, we could also call into other libraries. These choices should happen at the ASR level, and then the result further optimized (such as inlined) as needed.

Types

The argument types in args have the types of the corresponding signature as determined by intrinsic_id. For example IntegerAbs accepts an integer, but RealAbs accepts a real.

Examples

The following example code creates IntrinsicFunction ASR node:

sin(0.5)

ASR:

(TranslationUnit
    (SymbolTable
        1
        {

        })
    [(IntrinsicFunction
        [(RealConstant
            0.500000
            (Real 4 [])
        )]
        0
        0
        (Real 4 [])
        (RealConstant 0.479426 (Real 4 []))
    )]
)

See Also

FunctionCall