# LWE

## LWE attributes

### BitFieldEncodingAttr

An attribute describing encoded LWE plaintexts using bit fields.

Syntax:

```
#lwe.bit_field_encoding<
unsigned, # cleartext_start
unsigned # cleartext_bitwidth
>
```

A bit field encoding of an integer describes which contiguous region of bits a small integer occupies within a larger integer.

The data describing the encoding consists of the starting bit positions of
the cleartext bit field and its width, where the LSB is bit 0 and the MSB
is bit `bit_width-1`

. So the above example would have starting bit `30`

and
width `3`

. The bits not specified for the message have semantics defined
by the scheme or lowering.

Note that this encoding does not specify the underlying bit width of the plaintext space. This is left for lowerings to decide.

The presence of this attribute as the `encoding`

attribute of a tensor
indicates that the tensor is an LWE ciphertext.

**Example (CGGI):**

```
#encoding = #lwe.bit_field_encoding<cleartext_start=30, cleartext_bitwidth=3>
!plaintext = !lwe.lwe_plaintext<encoding = #encoding>
%0 = arith.constant 4 : i3
%1 = lwe.encode %0 { encoding = #encoding }: i3 to !plaintext
```

The above represents an LWE plaintext encoding the 3-bit cleartext 4 as an
LWE ciphertext in a 32-bit integer, with a single bit of padding at the MSB.
This corresponds to the following, where 0 denotes a 0 bit, `b`

denotes a
bit of the cleartext, `n`

denotes a bit reserved for noise, and `|`

is a
visual aid to show where the bit fields begin and end.

```
0|bbb|nn...n
MSB^ ^LSB
```

**Example (BGV):**

Note: BGV uses the RLWE encodings, but they have the same bit-field encoding attributes as here. So this example serves mainly to show how this attribute can be used to specify storing bits in the LSB of a plaintext.

```
#encoding = #lwe.bit_field_encoding<cleartext_start=4, cleartext_bitwidth=4>
!plaintext = !lwe.lwe_plaintext<encoding = #encoding>
%0 = arith.constant 9 : i4
%1 = lwe.encode %0 { encoding = #encoding }: i4 to !plaintext
```

The above represents an LWE plaintext encoding a 4-bit cleartext as an LWE ciphertext in the least-significant bits of a larger integer. This corresponds to the following.

```
nn...n|bbbb
MSB^ ^LSB
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

cleartext_start | `unsigned` | |

cleartext_bitwidth | `unsigned` |

### LWEParamsAttr

Syntax:

```
#lwe.lwe_params<
IntegerAttr, # cmod
unsigned # dimension
>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

cmod | `IntegerAttr` | |

dimension | `unsigned` |

### RLWEParamsAttr

Syntax:

```
#lwe.rlwe_params<
IntegerAttr, # cmod
unsigned, # dimension
unsigned # polyDegree
>
```

An attribute describing classical RLWE parameters:

`cmod`

: the coefficient modulus for the polynomials.`dimension`

: the number of polynomials used in an RLWE sample, analogous to LWEParams.dimension.`polyDegree`

: the degree $N$ of the negacyclic polynomial modulus $x^N + 1$.

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

cmod | `IntegerAttr` | |

dimension | `unsigned` | |

polyDegree | `unsigned` |

### UnspecifiedBitFieldEncodingAttr

An attribute describing unspecified bit field encodings.

Syntax:

```
#lwe.unspecified_bit_field_encoding<
unsigned # cleartext_bitwidth
>
```

See LWE_BitFieldEncoding for a description of bit field encodings.

This attribute describes an unspecified bit field encoding; this is where the starting bit position of the cleartext bit field is unspecified, but its width is fixed. A noise growth analysis should be performed to determine the optimal amount of bits needed for noise and padding to specify the bit field encodings starting bit position.

Example:

```
#lwe_encoding = #lwe.unspecified_bit_field_encoding<cleartext_bitwidth=3>
%lwe_ciphertext = arith.constant <[1,2,3,4]> : tensor<4xi32, #lwe_encoding>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

cleartext_bitwidth | `unsigned` |

### InverseCanonicalEmbeddingEncodingAttr

An attribute describing encoded RLWE plaintexts via the rounded inverse canonical embedding.

Syntax:

```
#lwe.inverse_canonical_embedding_encoding<
unsigned, # cleartext_start
unsigned # cleartext_bitwidth
>
```

Let $n$ be the degree of the polynomials in the plaintext space. An “inverse canonical embedding encoding” of a list of real or complex values $v_1, \dots, v_{n/2}$ is (almost) the inverse of the following decoding map.

Define a map $\tau_N$ that maps a polynomial $p \in \mathbb{Z}[x] / (x^N + 1) \to \mathbb{C}^{N/2}$ by evaluating it at the following $N/2$ points, where $\omega = e^{2 \pi i / 2N}$ is the primitive $2N$th root of unity:

[ \omega, \omega^3, \omega^5, \dots, \omega^{N-1} ]

Then the complete decoding operation is $\textup{Decode}(p) = (1/\Delta)\tau_N(p)$, where $\Delta$ is a scaling parameter and $\tau_N$ is the truncated canonical embedding above. The encoding operation is the inverse of the decoding operation, with some caveats explained below.

The map $\tau_N$ is derived from the so-called *canonical embedding*
$\tau$, though in the standard canonical embedding, we evaluate at all odd
powers of the root of unity, $\omega, \omega^3, \dots, \omega^{2N-1}$. For
polynomials in the slightly larger space $\mathbb{R}[x] / (x^N + 1)$, the
image of the canonical embedding is the subspace $H \subset \mathbb{C}^N$
defined by tuples $(z_1, \dots, z_N)$ such that $\overline{z_i} =
\overline{z_{N-i+1}}$. Note that this property holds because polynomial
evaluation commutes with complex conjugates, and the second half of the
roots of unity evaluate are complex conjugates of the first half. The
converse, that any such tuple with complex conjugate symmetry has an
inverse under $\tau$ with all real coefficients, makes $\tau$ is a
bijection onto $H$. $\tau$ and its inverse are explicitly computable as
discrete Fourier Transforms.

Because of the symmetry in canonical embedding for real polynomials, inputs to this encoding can be represented as a list of $N/2$ complex points, with the extra symmetric structure left implicit. $\tau_N$ and its inverse can also be explicitly computed without need to expand the vectors to length $N$.

The rounding step is required to invert the decoding because, while
cleartexts must be (implicitly) in the subspace $H$, they need not be the
output of $\tau_N$ for an *integer* polynomial. The rounding step ensures
we can use integer polynomial plaintexts for the FHE operations. There are
multiple rounding mechanisms, and this attribute does not specify which is
used, because in theory two ciphertexts that have used different roundings
are still compatible, though they may have different noise growth patterns.

The scaling parameter $\Delta$ is specified by the `cleartext_start`

and
`cleartext_bitwidth`

parameters, which are applied coefficient-wise using
the same semantics as the `bit_field_encoding`

.

This attribute can be used in multiple ways:

- On a
`poly.poly`

, it asserts that the polynomial has been transformed from a coefficient list using the canonical embedding. - On a tensor of
`poly.poly`

, it asserts that the tensor is an RLWE ciphertext for some RLWE scheme that supports the approximate embedding encoding.

A typical flow for the CKKS scheme using this encoding would be to apply an inverse FFT operation to invert the canonical embedding to be a polynomial with real coefficients, then encrypt scale the resulting polynomial’s coefficients according to the scaling parameters, then round to get integer coefficients.

Example:

```
#generator = #poly.polynomial<1 + x**1024>
#ring = #poly.ring<cmod=65536, ideal=#generator>
#lwe_encoding = #lwe.polynomial_evaluation_encoding<cleartext_start=30, cleartext_bitwidth=3>
%evals = arith.constant <[1, 2, 4, 5]> : tensor<4xi16>
// TODO(#182): fix docs
// Note no `intt` operation exists in poly yet.
%poly1 = poly.intt %evals : tensor<4xi16> -> !poly.poly<#ring, #eval_encoding>
%poly2 = poly.intt %evals : tensor<4xi16> -> !poly.poly<#ring, #eval_encoding>
%rlwe_ciphertext = tensor.from_elements %poly1, %poly2 : tensor<2x!poly.poly<#ring, #eval_encoding>>
```

See `bit_field_encoding`

for the definition of the `cleartext_start`

and
`cleartext_bitwidth`

fields.

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

cleartext_start | `unsigned` | |

cleartext_bitwidth | `unsigned` |

### PolynomialCoefficientEncodingAttr

An attribute describing encoded RLWE plaintexts via coefficients.

Syntax:

```
#lwe.polynomial_coefficient_encoding<
unsigned, # cleartext_start
unsigned # cleartext_bitwidth
>
```

A coefficient encoding of a list of integers asserts that the coefficients
of the polynomials contain the cleartexts, with the same semantics as
`bit_field_encoding`

for per-coefficient encodings.

The presence of this attribute as the `encoding`

attribute of a tensor of
`poly.poly`

indicates that the tensor is an RLWE ciphertext for some RLWE
scheme that supports the coefficient encoding.

See `bit_field_encoding`

for the definition of the `cleartext_start`

and
`cleartext_bitwidth`

fields.

Example:

```
#generator = #poly.polynomial<1 + x**1024>
#ring = #poly.ring<cmod=65536, ideal=#generator>
#coeff_encoding = #lwe.polynomial_coefficient_encoding<cleartext_start=15, cleartext_bitwidth=4>
%poly1 = poly.from_tensor %coeffs1 : tensor<10xi16> -> !poly.poly<#ring>
%poly2 = poly.from_tensor %coeffs2 : tensor<10xi16> -> !poly.poly<#ring>
%rlwe_ciphertext = tensor.from_elements %poly1, %poly2 : tensor<2x!poly.poly<#ring>, #coeff_encoding>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

cleartext_start | `unsigned` | |

cleartext_bitwidth | `unsigned` |

### PolynomialEvaluationEncodingAttr

An attribute describing encoded RLWE plaintexts via evaluations at fixed points.

Syntax:

```
#lwe.polynomial_evaluation_encoding<
unsigned, # cleartext_start
unsigned # cleartext_bitwidth
>
```

A “evaluation encoding” of a list of integers $(v_1, \dots, v_n)$ asserts
that $f(x_1 ) = v_1, \dots, f(x_n) = v_n$ for some implicit, but fixed and
distinct choice of inputs $x_i$. The encoded values are also scaled by a
scale factor, having the same semantics as `bit_field_encoding`

, but
applied entry-wise (to either the coefficient or evaluation representation).

This attribute can be used in multiple ways:

- On a
`poly.poly`

, it asserts that the polynomial has been transformed from an evaluation tensor. - On a tensor of
`poly.poly`

, it asserts that the tensor is an RLWE ciphertext for some RLWE scheme that supports the evaluation encoding.

A typical workflow for the BFV/BGV schemes using this encoding would be to apply a INTT operation to the input list of cleartexts to convert from evaluation form to coefficient form, then encrypt the resulting polynomial in coefficient form, then apply NTT back to the evaluation form for faster multiplication of ciphertexts.

The points chosen are fixed to be the powers of a primitive root of unity of the coefficient ring of the plaintext space, which allows one to use NTT/INTT to tansform quickly between the coefficient and evaluation forms.

Example:

```
#generator = #poly.polynomial<1 + x**1024>
// note that the cmod should be chosen so as to ensure a primitive root of
// unity exists in the multiplicative group (Z / cmod Z)^*
#ring = #poly.ring<cmod=65536, ideal=#generator>
#lwe_encoding = #lwe.polynomial_evaluation_encoding<cleartext_start=30, cleartext_bitwidth=3>
%evals = arith.constant <[1, 2, 4, 5]> : tensor<4xi16>
// TODO(#182): fix docs
// Note no `intt` operation exists in poly yet.
%poly1 = poly.intt %evals : tensor<4xi16> -> !poly.poly<#ring, #eval_encoding>
%poly2 = poly.intt %evals : tensor<4xi16> -> !poly.poly<#ring, #eval_encoding>
%rlwe_ciphertext = tensor.from_elements %poly1, %poly2 : tensor<2x!poly.poly<#ring, #eval_encoding>>
```

See `bit_field_encoding`

for the definition of the `cleartext_start`

and
`cleartext_bitwidth`

fields.

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

cleartext_start | `unsigned` | |

cleartext_bitwidth | `unsigned` |

## LWE types

### BFloat16Type

bfloat16 floating-point type

### ComplexType

Complex number with a parameterized element type

Syntax:

```
complex-type ::= `complex` `<` type `>`
```

The value of `complex`

type represents a complex number with a parameterized
element type, which is composed of a real and imaginary value of that
element type. The element must be a floating point or integer scalar type.

#### Example:

```
complex<f32>
complex<i32>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

elementType | `Type` |

### Float8E4M3B11FNUZType

8-bit floating point with 3 bit mantissa

An 8-bit floating point type with 1 sign bit, 4 bits exponent and 3 bits mantissa. This is not a standard type as defined by IEEE-754, but it follows similar conventions, with the exception that there are no infinity values, no negative zero, and only one NaN representation. This type has the following characteristics:

- bit encoding: S1E4M3
- exponent bias: 11
- infinities: Not supported
- NaNs: Supported with sign bit set to 1, exponent bits and mantissa bits set to all 0s
- denormals when exponent is 0

Related to: https://dl.acm.org/doi/10.5555/3454287.3454728

### Float8E4M3FNType

8-bit floating point with 3 bit mantissa

An 8-bit floating point type with 1 sign bit, 4 bits exponent and 3 bits mantissa. This is not a standard type as defined by IEEE-754, but it follows similar conventions, with the exception that there are no infinity values and only two NaN representations. This type has the following characteristics:

- bit encoding: S1E4M3
- exponent bias: 7
- infinities: Not supported
- NaNs: supported with exponent bits and mantissa bits set to all 1s
- denormals when exponent is 0

Described in: https://arxiv.org/abs/2209.05433

### Float8E4M3FNUZType

8-bit floating point with 3 bit mantissa

An 8-bit floating point type with 1 sign bit, 4 bits exponent and 3 bits mantissa. This is not a standard type as defined by IEEE-754, but it follows similar conventions, with the exception that there are no infinity values, no negative zero, and only one NaN representation. This type has the following characteristics:

- bit encoding: S1E4M3
- exponent bias: 8
- infinities: Not supported
- NaNs: Supported with sign bit set to 1, exponent bits and mantissa bits set to all 0s
- denormals when exponent is 0

Described in: https://arxiv.org/abs/2209.05433

### Float8E5M2Type

8-bit floating point with 2 bit mantissa

An 8-bit floating point type with 1 sign bit, 5 bits exponent and 2 bits mantissa. This is not a standard type as defined by IEEE-754, but it follows similar conventions with the following characteristics:

- bit encoding: S1E5M2
- exponent bias: 15
- infinities: supported with exponent set to all 1s and mantissa 0s
- NaNs: supported with exponent bits set to all 1s and mantissa of (01, 10, or 11)
- denormals when exponent is 0

Described in: https://arxiv.org/abs/2209.05433

### Float8E5M2FNUZType

8-bit floating point with 2 bit mantissa

An 8-bit floating point type with 1 sign bit, 5 bits exponent and 2 bits mantissa. This is not a standard type as defined by IEEE-754, but it follows similar conventions, with the exception that there are no infinity values, no negative zero, and only one NaN representation. This type has the following characteristics:

- bit encoding: S1E5M2
- exponent bias: 16
- infinities: Not supported
- NaNs: Supported with sign bit set to 1, exponent bits and mantissa bits set to all 0s
- denormals when exponent is 0

Described in: https://arxiv.org/abs/2206.02915

### Float16Type

16-bit floating-point type

### Float32Type

32-bit floating-point type

### Float64Type

64-bit floating-point type

### Float80Type

80-bit floating-point type

### Float128Type

128-bit floating-point type

### FloatTF32Type

TF32 floating-point type

### FunctionType

Map from a list of inputs to a list of results

Syntax:

```
// Function types may have multiple results.
function-result-type ::= type-list-parens | non-function-type
function-type ::= type-list-parens `->` function-result-type
```

The function type can be thought of as a function signature. It consists of a list of formal parameter types and a list of formal result types.

#### Example:

```
func.func @add_one(%arg0 : i64) -> i64 {
%c1 = arith.constant 1 : i64
%0 = arith.addi %arg0, %c1 : i64
return %0 : i64
}
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

inputs | `ArrayRef<Type>` | |

results | `ArrayRef<Type>` |

### IndexType

Integer-like type with unknown platform-dependent bit width

Syntax:

```
// Target word-sized integer.
index-type ::= `index`
```

The index type is a signless integer whose size is equal to the natural machine word of the target ( rationale ) and is used by the affine constructs in MLIR.

**Rationale:** integers of platform-specific bit widths are practical to
express sizes, dimensionalities and subscripts.

### IntegerType

Integer type with arbitrary precision up to a fixed limit

Syntax:

```
// Sized integers like i1, i4, i8, i16, i32.
signed-integer-type ::= `si` [1-9][0-9]*
unsigned-integer-type ::= `ui` [1-9][0-9]*
signless-integer-type ::= `i` [1-9][0-9]*
integer-type ::= signed-integer-type |
unsigned-integer-type |
signless-integer-type
```

Integer types have a designated bit width and may optionally have signedness semantics.

**Rationale:** low precision integers (like `i2`

, `i4`

etc) are useful for
low-precision inference chips, and arbitrary precision integers are useful
for hardware synthesis (where a 13 bit multiplier is a lot cheaper/smaller
than a 16 bit one).

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

width | `unsigned` | |

signedness | `SignednessSemantics` |

### MemRefType

Shaped reference to a region of memory

Syntax:

```
layout-specification ::= attribute-value
memory-space ::= attribute-value
memref-type ::= `memref` `<` dimension-list-ranked type
(`,` layout-specification)? (`,` memory-space)? `>`
```

A `memref`

type is a reference to a region of memory (similar to a buffer
pointer, but more powerful). The buffer pointed to by a memref can be
allocated, aliased and deallocated. A memref can be used to read and write
data from/to the memory region which it references. Memref types use the
same shape specifier as tensor types. Note that `memref<f32>`

,
`memref<0 x f32>`

, `memref<1 x 0 x f32>`

, and `memref<0 x 1 x f32>`

are all
different types.

A `memref`

is allowed to have an unknown rank (e.g. `memref<*xf32>`

). The
purpose of unranked memrefs is to allow external library functions to
receive memref arguments of any rank without versioning the functions based
on the rank. Other uses of this type are disallowed or will have undefined
behavior.

Are accepted as elements:

- built-in integer types;
- built-in index type;
- built-in floating point types;
- built-in vector types with elements of the above types;
- another memref type;
- any other type implementing
`MemRefElementTypeInterface`

.

##### Layout

A memref may optionally have a layout that indicates how indices are
transformed from the multi-dimensional form into a linear address. The
layout must avoid internal aliasing, i.e., two distinct tuples of
*in-bounds* indices must be pointing to different elements in memory. The
layout is an attribute that implements `MemRefLayoutAttrInterface`

. The
bulitin dialect offers two kinds of layouts: strided and affine map, each
of which is available as an attribute. Other attributes may be used to
represent the layout as long as they can be converted to a
semi-affine map and implement the required
interface. Users of memref are expected to fallback to the affine
representation when handling unknown memref layouts. Multi-dimensional
affine forms are interpreted in *row-major* fashion.

In absence of an explicit layout, a memref is considered to have a
multi-dimensional identity affine map layout. Identity layout maps do not
contribute to the MemRef type identification and are discarded on
construction. That is, a type with an explicit identity map is
`memref<?x?xf32, (i,j)->(i,j)>`

is strictly the same as the one without a
layout, `memref<?x?xf32>`

.

##### Affine Map Layout

The layout may be represented directly as an affine map from the index space
to the storage space. For example, the following figure shows an index map
which maps a 2-dimensional index from a 2x2 index space to a 3x3 index
space, using symbols `S0`

and `S1`

as offsets.

Semi-affine maps are sufficiently flexible to represent a wide variety of dense storage layouts, including row- and column-major and tiled:

```
// MxN matrix stored in row major layout in memory:
#layout_map_row_major = (i, j) -> (i, j)
// MxN matrix stored in column major layout in memory:
#layout_map_col_major = (i, j) -> (j, i)
// MxN matrix stored in a 2-d blocked/tiled layout with 64x64 tiles.
#layout_tiled = (i, j) -> (i floordiv 64, j floordiv 64, i mod 64, j mod 64)
```

##### Strided Layout

Memref layout can be expressed using strides to encode the distance, in
number of elements, in (linear) memory between successive entries along a
particular dimension. For example, a row-major strided layout for
`memref<2x3x4xf32>`

is `strided<[12, 4, 1]>`

, where the last dimension is
contiguous as indicated by the unit stride and the remaining strides are
products of the sizes of faster-variying dimensions. Strided layout can also
express non-contiguity, e.g., `memref<2x3, strided<[6, 2]>>`

only accesses
even elements of the dense consecutive storage along the innermost
dimension.

The strided layout supports an optional *offset* that indicates the
distance, in the number of elements, between the beginning of the memref
and the first accessed element. When omitted, the offset is considered to
be zero. That is, `memref<2, strided<[2], offset: 0>>`

and
`memref<2, strided<[2]>>`

are strictly the same type.

Both offsets and strides may be *dynamic*, that is, unknown at compile time.
This is represented by using a question mark (`?`

) instead of the value in
the textual form of the IR.

The strided layout converts into the following canonical one-dimensional affine form through explicit linearization:

```
affine_map<(d0, ... dN)[offset, stride0, ... strideN] ->
(offset + d0 * stride0 + ... dN * strideN)>
```

Therefore, it is never subject to the implicit row-major layout interpretation.

##### Codegen of Unranked Memref

Using unranked memref in codegen besides the case mentioned above is highly discouraged. Codegen is concerned with generating loop nests and specialized instructions for high-performance, unranked memref is concerned with hiding the rank and thus, the number of enclosing loops required to iterate over the data. However, if there is a need to code-gen unranked memref, one possible path is to cast into a static ranked type based on the dynamic rank. Another possible path is to emit a single while loop conditioned on a linear index and perform delinearization of the linear index to a dynamic array containing the (unranked) indices. While this is possible, it is expected to not be a good idea to perform this during codegen as the cost of the translations is expected to be prohibitive and optimizations at this level are not expected to be worthwhile. If expressiveness is the main concern, irrespective of performance, passing unranked memrefs to an external C++ library and implementing rank-agnostic logic there is expected to be significantly simpler.

Unranked memrefs may provide expressiveness gains in the future and help bridge the gap with unranked tensors. Unranked memrefs will not be expected to be exposed to codegen but one may query the rank of an unranked memref (a special op will be needed for this purpose) and perform a switch and cast to a ranked memref as a prerequisite to codegen.

Example:

```
// With static ranks, we need a function for each possible argument type
%A = alloc() : memref<16x32xf32>
%B = alloc() : memref<16x32x64xf32>
call @helper_2D(%A) : (memref<16x32xf32>)->()
call @helper_3D(%B) : (memref<16x32x64xf32>)->()
// With unknown rank, the functions can be unified under one unranked type
%A = alloc() : memref<16x32xf32>
%B = alloc() : memref<16x32x64xf32>
// Remove rank info
%A_u = memref_cast %A : memref<16x32xf32> -> memref<*xf32>
%B_u = memref_cast %B : memref<16x32x64xf32> -> memref<*xf32>
// call same function with dynamic ranks
call @helper(%A_u) : (memref<*xf32>)->()
call @helper(%B_u) : (memref<*xf32>)->()
```

The core syntax and representation of a layout specification is a
semi-affine map. Additionally,
syntactic sugar is supported to make certain layout specifications more
intuitive to read. For the moment, a `memref`

supports parsing a strided
form which is converted to a semi-affine map automatically.

The memory space of a memref is specified by a target-specific attribute. It might be an integer value, string, dictionary or custom dialect attribute. The empty memory space (attribute is None) is target specific.

The notionally dynamic value of a memref value includes the address of the buffer allocated, as well as the symbols referred to by the shape, layout map, and index maps.

Examples of memref static type

```
// Identity index/layout map
#identity = affine_map<(d0, d1) -> (d0, d1)>
// Column major layout.
#col_major = affine_map<(d0, d1, d2) -> (d2, d1, d0)>
// A 2-d tiled layout with tiles of size 128 x 256.
#tiled_2d_128x256 = affine_map<(d0, d1) -> (d0 div 128, d1 div 256, d0 mod 128, d1 mod 256)>
// A tiled data layout with non-constant tile sizes.
#tiled_dynamic = affine_map<(d0, d1)[s0, s1] -> (d0 floordiv s0, d1 floordiv s1,
d0 mod s0, d1 mod s1)>
// A layout that yields a padding on two at either end of the minor dimension.
#padded = affine_map<(d0, d1) -> (d0, (d1 + 2) floordiv 2, (d1 + 2) mod 2)>
// The dimension list "16x32" defines the following 2D index space:
//
// { (i, j) : 0 <= i < 16, 0 <= j < 32 }
//
memref<16x32xf32, #identity>
// The dimension list "16x4x?" defines the following 3D index space:
//
// { (i, j, k) : 0 <= i < 16, 0 <= j < 4, 0 <= k < N }
//
// where N is a symbol which represents the runtime value of the size of
// the third dimension.
//
// %N here binds to the size of the third dimension.
%A = alloc(%N) : memref<16x4x?xf32, #col_major>
// A 2-d dynamic shaped memref that also has a dynamically sized tiled
// layout. The memref index space is of size %M x %N, while %B1 and %B2
// bind to the symbols s0, s1 respectively of the layout map #tiled_dynamic.
// Data tiles of size %B1 x %B2 in the logical space will be stored
// contiguously in memory. The allocation size will be
// (%M ceildiv %B1) * %B1 * (%N ceildiv %B2) * %B2 f32 elements.
%T = alloc(%M, %N) [%B1, %B2] : memref<?x?xf32, #tiled_dynamic>
// A memref that has a two-element padding at either end. The allocation
// size will fit 16 * 64 float elements of data.
%P = alloc() : memref<16x64xf32, #padded>
// Affine map with symbol 's0' used as offset for the first dimension.
#imapS = affine_map<(d0, d1) [s0] -> (d0 + s0, d1)>
// Allocate memref and bind the following symbols:
// '%n' is bound to the dynamic second dimension of the memref type.
// '%o' is bound to the symbol 's0' in the affine map of the memref type.
%n = ...
%o = ...
%A = alloc (%n)[%o] : <16x?xf32, #imapS>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

shape | `::llvm::ArrayRef<int64_t>` | |

elementType | `Type` | |

layout | `MemRefLayoutAttrInterface` | |

memorySpace | `Attribute` |

### NoneType

A unit type

Syntax:

```
none-type ::= `none`
```

NoneType is a unit type, i.e. a type with exactly one possible value, where its value does not have a defined dynamic representation.

#### Example:

```
func.func @none_type() {
%none_val = "foo.unknown_op"() : () -> none
return
}
```

### OpaqueType

Type of a non-registered dialect

Syntax:

```
opaque-type ::= `opaque` `<` type `>`
```

Opaque types represent types of non-registered dialects. These are types represented in their raw string form, and can only usefully be tested for type equality.

#### Example:

```
opaque<"llvm", "struct<(i32, float)>">
opaque<"pdl", "value">
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

dialectNamespace | `StringAttr` | |

typeData | `::llvm::StringRef` |

### RankedTensorType

Multi-dimensional array with a fixed number of dimensions

Syntax:

```
tensor-type ::= `tensor` `<` dimension-list type (`,` encoding)? `>`
dimension-list ::= (dimension `x`)*
dimension ::= `?` | decimal-literal
encoding ::= attribute-value
```

Values with tensor type represents aggregate N-dimensional data values, and
have a known element type and a fixed rank with a list of dimensions. Each
dimension may be a static non-negative decimal constant or be dynamically
determined (indicated by `?`

).

The runtime representation of the MLIR tensor type is intentionally
abstracted - you cannot control layout or get a pointer to the data. For
low level buffer access, MLIR has a `memref`

type. This
abstracted runtime representation holds both the tensor data values as well
as information about the (potentially dynamic) shape of the tensor. The
`dim`

operation returns the size of a
dimension from a value of tensor type.

The `encoding`

attribute provides additional information on the tensor.
An empty attribute denotes a straightforward tensor without any specific
structure. But particular properties, like sparsity or other specific
characteristics of the data of the tensor can be encoded through this
attribute. The semantics are defined by a type and attribute interface
and must be respected by all passes that operate on tensor types.
TODO: provide this interface, and document it further.

Note: hexadecimal integer literals are not allowed in tensor type
declarations to avoid confusion between `0xf32`

and `0 x f32`

. Zero sizes
are allowed in tensors and treated as other sizes, e.g.,
`tensor<0 x 1 x i32>`

and `tensor<1 x 0 x i32>`

are different types. Since
zero sizes are not allowed in some other types, such tensors should be
optimized away before lowering tensors to vectors.

#### Example:

```
// Known rank but unknown dimensions.
tensor<? x ? x ? x ? x f32>
// Partially known dimensions.
tensor<? x ? x 13 x ? x f32>
// Full static shape.
tensor<17 x 4 x 13 x 4 x f32>
// Tensor with rank zero. Represents a scalar.
tensor<f32>
// Zero-element dimensions are allowed.
tensor<0 x 42 x f32>
// Zero-element tensor of f32 type (hexadecimal literals not allowed here).
tensor<0xf32>
// Tensor with an encoding attribute (where #ENCODING is a named alias).
tensor<?x?xf64, #ENCODING>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

shape | `::llvm::ArrayRef<int64_t>` | |

elementType | `Type` | |

encoding | `Attribute` |

### TupleType

Fixed-sized collection of other types

Syntax:

```
tuple-type ::= `tuple` `<` (type ( `,` type)*)? `>`
```

The value of `tuple`

type represents a fixed-size collection of elements,
where each element may be of a different type.

**Rationale:** Though this type is first class in the type system, MLIR
provides no standard operations for operating on `tuple`

types
(rationale).

#### Example:

```
// Empty tuple.
tuple<>
// Single element
tuple<f32>
// Many elements.
tuple<i32, f32, tensor<i1>, i5>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

types | `ArrayRef<Type>` |

### UnrankedMemRefType

Shaped reference, with unknown rank, to a region of memory

Syntax:

```
unranked-memref-type ::= `memref` `<*x` type (`,` memory-space)? `>`
memory-space ::= attribute-value
```

A `memref`

type with an unknown rank (e.g. `memref<*xf32>`

). The purpose of
unranked memrefs is to allow external library functions to receive memref
arguments of any rank without versioning the functions based on the rank.
Other uses of this type are disallowed or will have undefined behavior.

See MemRefType for more information on memref types.

#### Examples:

```
memref<*f32>
// An unranked memref with a memory space of 10.
memref<*f32, 10>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

elementType | `Type` | |

memorySpace | `Attribute` |

### UnrankedTensorType

Multi-dimensional array with unknown dimensions

Syntax:

```
tensor-type ::= `tensor` `<` `*` `x` type `>`
```

An unranked tensor is a type of tensor in which the set of dimensions have unknown rank. See RankedTensorType for more information on tensor types.

#### Examples:

```
tensor<*xf32>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

elementType | `Type` |

### VectorType

Multi-dimensional SIMD vector type

Syntax:

```
vector-type ::= `vector` `<` vector-dim-list vector-element-type `>`
vector-element-type ::= float-type | integer-type | index-type
vector-dim-list := (static-dim-list `x`)?
static-dim-list ::= static-dim (`x` static-dim)*
static-dim ::= (decimal-literal | `[` decimal-literal `]`)
```

The vector type represents a SIMD style vector used by target-specific operation sets like AVX or SVE. While the most common use is for 1D vectors (e.g. vector<16 x f32>) we also support multidimensional registers on targets that support them (like TPUs). The dimensions of a vector type can be fixed-length, scalable, or a combination of the two. The scalable dimensions in a vector are indicated between square brackets ([ ]).

Vector shapes must be positive decimal integers. 0D vectors are allowed by
omitting the dimension: `vector<f32>`

.

Note: hexadecimal integer literals are not allowed in vector type
declarations, `vector<0x42xi32>`

is invalid because it is interpreted as a
2D vector with shape `(0, 42)`

and zero shapes are not allowed.

#### Examples:

```
// A 2D fixed-length vector of 3x42 i32 elements.
vector<3x42xi32>
// A 1D scalable-length vector that contains a multiple of 4 f32 elements.
vector<[4]xf32>
// A 2D scalable-length vector that contains a multiple of 2x8 f32 elements.
vector<[2]x[8]xf32>
// A 2D mixed fixed/scalable vector that contains 4 scalable vectors of 4 f32 elements.
vector<4x[4]xf32>
// A 3D mixed fixed/scalable vector in which only the inner dimension is
// scalable.
vector<2x[4]x8xf32>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

shape | `::llvm::ArrayRef<int64_t>` | |

elementType | `Type` | |

scalableDims | `::llvm::ArrayRef<bool>` |

### LWECiphertextType

A type for LWE ciphertexts

Syntax:

```
!lwe.lwe_ciphertext<
::mlir::Attribute, # encoding
LWEParamsAttr # lwe_params
>
```

A type for LWE ciphertexts.

This type keeps track of the plaintext integer encoding for the LWE Ciphertext to ensure proper decoding after decryption. It also keeps track of the ring where the LWE ciphertext is defined, which provides information on the ciphertext shape and the ring operations used in LWE operations.

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

encoding | `::mlir::Attribute` | |

lwe_params | `LWEParamsAttr` |

### LWEPlaintextType

A type for LWE plaintexts

Syntax:

```
!lwe.lwe_plaintext<
::mlir::Attribute # encoding
>
```

A type for LWE plaintexts.

This type keeps track of the plaintext integer encoding for the LWE plaintext before it is encrypted.

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

encoding | `::mlir::Attribute` |

### RLWECiphertextType

A type for RLWE ciphertexts

Syntax:

```
!lwe.rlwe_ciphertext<
::mlir::Attribute, # encoding
RLWEParamsAttr # rlwe_params
>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

encoding | `::mlir::Attribute` | |

rlwe_params | `RLWEParamsAttr` |

### RLWEPlaintextType

A type for RLWE plaintexts

Syntax:

```
!lwe.rlwe_plaintext<
::mlir::Attribute # encoding
>
```

#### Parameters:

Parameter | C++ type | Description |
---|---|---|

encoding | `::mlir::Attribute` |

## LWE ops

`lwe.encode`

(heir::lwe::EncodeOp)

*Encode an integer to yield an LWE plaintext*

Syntax:

```
operation ::= `lwe.encode` $plaintext attr-dict `:` qualified(type($plaintext)) `to` qualified(type($output))
```

Encode an integer to yield an LWE plaintext.

This op uses a an encoding attribute to encode the bits of the integer into an LWE plaintext value that can then be encrypted.

Examples:

```
%Y = lwe.encode %value {encoding = #enc}: i1 to !lwe.lwe_plaintext<encoding = #enc, ring = #ring>
```

Traits: `AlwaysSpeculatableImplTrait`

Interfaces: `ConditionallySpeculatable`

, `NoMemoryEffect (MemoryEffectOpInterface)`

Effects: `MemoryEffects::Effect{}`

#### Attributes:

Attribute | MLIR Type | Description |
---|---|---|

`encoding` | ::mlir::Attribute | An attribute describing encoded LWE plaintexts using bit fields. or An attribute describing unspecified bit field encodings. |

#### Operands:

Operand | Description |
---|---|

`plaintext` | signless-integer-like |

#### Results:

Result | Description |
---|---|

`output` | A type for LWE plaintexts |

`lwe.trivial_encrypt`

(heir::lwe::TrivialEncryptOp)

*Create a trivial encryption of a plaintext.*

Syntax:

```
operation ::= `lwe.trivial_encrypt` operands attr-dict `:` qualified(type(operands)) `to` qualified(type(results))
```

Traits: `AlwaysSpeculatableImplTrait`

Interfaces: `ConditionallySpeculatable`

, `NoMemoryEffect (MemoryEffectOpInterface)`

Effects: `MemoryEffects::Effect{}`

#### Attributes:

Attribute | MLIR Type | Description |
---|---|---|

`params` | ::mlir::heir::lwe::LWEParamsAttr |

#### Operands:

Operand | Description |
---|---|

`input` | A type for LWE plaintexts |

#### Results:

Result | Description |
---|---|

`output` | A type for LWE ciphertexts |