---
# System prepended metadata

title: '[GT4Py] Research: FOIR'
tags: [cycle20]

---

# [GT4Py] Research: FOIR

<!-- Add the tag for the current cycle number in the top bar -->

- Shaped by: Till
- Appetite (FTEs, weeks):
- Developers: Till<!-- Filled in at the betting table unless someone is specifically required here -->

## Problem

ITIR is not well suited for many optimizations and blocks progress on features we need for both good runtime performance as well as good compile time performance.

- Compile time performance: We can not extract temporaries from `if_` clauses (to avoid out of bounds accesses), but they frequently occur to model boundary conditions. Without being able to extract temporaries we need to force inline the lift statements leading to an explosion of the tree. For many fused stencils this is still managable right now, but this is partially a problem again.
- Runtime performance: Not being able to place temporaries hurts performance. We don't know how severe this really is, but it should be fixed. 
<!-- The raw idea, a use case, or something we’ve seen that motivates us to work on this -->

## Appetite
<!-- Explain how much time we want to spend and how that constrains the solution -->

## Solution

The general direction of the project is to implement a combined IR which combines a local (iterator) and a global perspective (fields) in one IR. Both views are an extreme of the combined IR: All operations are either between iterators or between fields. There are still some design questions open with respect to this IR (both with respect to the specifications of the builtins, but also how we represent these nodes in the toolchain) that require thorough discussions and planing before we can move forward with implementation. This project does __not__ aim to solve these questions, but instead aims to only implement and experiment with the parts needed to solve the problems mentioned above. Component-wise this means we want to explore how to lower from FOAST + PAST to a functional IR that is able to model operations between fields and then further how to transform this into ITIR.

<!-- The core elements we came up with, presented in a form that’s easy for people to immediately understand -->

## Rabbit holes
<!-- Details about the solution worth calling out to avoid problems -->

## No-gos
<!-- Anything specifically excluded from the concept: functionality or use cases we intentionally aren’t covering to fit the ## appetite or make the problem tractable -->

## Progress

## Scratchpad

__FOIR__

- Lambda functions
- Function calls
- Arithmetic builtins between fields, e.g. `plus(a: Scalar | Field, b: Scalar | Field) -> Scalar | Field`
- `if(cond: Scalar, true_branch: A, false_branch: A) -> A`  
  `old_where(cond: Field, true_branch: A, false_branch: A) -> A`  
  where `A = Scalar | Field | Tuple[A, ...]`  (note that the dimensions of the field are allowed to differ as long as they are compatible).
  The semantic of the `old_where` is as it is currently implemented in FOAST, e.g. the result is defined everywhere where `cond` is true and `true_branch` is defined and everywhere where `cond` is false and `false_branch` is defined. The name `old_where` is just a placeholder.
- `scan`s: `scan(scan_pass: Lambda[[Scalar, ...], Scalar], axis: Dimension, forward: bool)`
- `cast(Scalar | Field, Type)`
- `broadcast(Scalar | Field, tuple[Dimension, ...])`
- `max_over(Field)`, `min_over(Field)`, `neighbor_sum(Field)`

__Passes to move to FOIR__

- CollapseTuple
- TemporaryPass -> not needed anymore

Scratchpad: 

Without control flow on ITIR propagte if to make lowering easy and have good compile times.

### Components

#### FOAST -> FOIR

```python
@gtx.field_operator
def lap(
    in_field: gtx.Field[[IDim, JDim], float]
) -> gtx.Field[[IDim, JDim], float]:
    return (
        -4.0 * in_field
        + in_field(Ioff[1])
        + in_field(Joff[1])
        + in_field(Ioff[-1])
        + in_field(Joff[-1])
    )


@gtx.field_operator
def laplap(
    in_field: gtx.Field[[IDim, JDim], float]
) -> gtx.Field[[IDim, JDim], float]:
    tmp = lap(in_field)
    return lap(tmp)
```

#### FOIR -> ITIR

```
(let lap )
(let (tmp -4*in+in(I+1)+in(J+1)+in(I-1)+in(J-1)
  )
```