102 IREE util Dialect
The IREE util dialect is the common support dialect used across IREE’s compiler pipeline. It provides shared types, attributes, structural operations, globals, lists, byte buffers, compiler hints, and runtime-facing utility operations that other IREE dialects build on.
For a beginner, the easiest way to understand util is to think of it as IREE’s bridge between generic MLIR and IREE’s runtime model. A frontend may start with func.func, tensors, memrefs, constants, and ordinary MLIR control flow. IREE eventually needs module initialization, global state, callable functions with ABI details, byte buffers, status checks, resource constants, and enough metadata to drive codegen and runtime serialization. The util dialect carries those concepts.
This dialect is not a high-level math dialect. It does not describe neural network layers, tensor algebra, GPU kernels, or executable binaries by itself. Instead, it supplies the common infrastructure that lets higher-level and lower-level IREE dialects cooperate.
102.1 Why This Dialect Exists
MLIR has many generic dialects, but IREE needs a portable runtime-facing layer. For example, MLIR memref is a compiler abstraction for memory references; IREE’s runtime often wants a byte buffer with explicit offsets and lengths. MLIR func.func is a generic function; IREE needs a function operation that can carry tied operand information, ABI reflection, inlining policy, and runtime interop metadata. MLIR attributes can inline large dense constants; IREE needs resource-backed and serializable forms that do not explode compiler memory use.
The util dialect exists to express those cross-cutting ideas in one place. It is deliberately broad. It has function-like operations, global variables, list and buffer operations, type conversion helpers, compiler assumptions, and debug-only operations. Many IREE passes depend on it because it provides a stable shared vocabulary.
The dialect also gives IREE a place to encode analyses and decisions. util.assume.int stores integer range and divisibility assumptions. util.numeric.optional_narrow records that a numeric narrowing is valid but optional. util.hoistable_conversion marks conversions that can be moved into globals. util.optimization_barrier blocks otherwise tempting rewrites when preserving a value boundary matters.
102.2 When To Use It
You will usually read util IR when you are looking at IREE before or after input conversion, global optimization, executable packaging, or VM/runtime lowering. It appears in modules that need initialization order, global constants, imported resources, serialized data, or runtime callable functions.
Use util when you need to understand how IREE represents host-side program structure. A util.initializer describes module initialization. A util.global describes state or constant storage. A util.func describes an IREE function. util.call and util.return describe calls and returns in that structural world.
Use the buffer operations when the compiler has moved from typed memory references to byte-addressed runtime storage. A !util.buffer is a reference-counted byte buffer. Operations such as util.buffer.load, util.buffer.store, util.buffer.copy, and util.buffer.fill operate in byte offsets and lengths.
Avoid reading util as though it were one cohesive source language. It is more like a toolbox. The operations are grouped by purpose, and many only make sense in a particular compiler phase.
102.3 Core Types And Attributes
The major util types are:
| Type | Meaning |
|---|---|
!util.buffer |
A reference-counted byte buffer modeled as pointer, offset, and length. |
!util.list<T> |
A dense typed list container. |
!util.ptr<T> |
A typed indirect reference to a runtime-addressable value. |
!util.object |
A placeholder for an unspecified runtime object. |
!util.unused |
A placeholder type used when a verifier needs a type even though the value is unused. |
!util.variant |
A runtime variant placeholder, often printed conceptually as ?. |
The common util attributes are:
| Attribute | Meaning |
|---|---|
#util.int.assumption |
Integer assumptions such as unsigned minimum, maximum, or divisibility. |
#util.byte_pattern |
Serializable repeated byte pattern storage. |
#util.byte_range |
Offset and length in bytes. |
#util.composite |
A concatenation of serializable attributes into one byte sequence. |
#util.inline.never |
Inlining policy that disables inlining. |
#util.inline.always |
Inlining policy that requests inlining when legal. |
#util.null |
A typed null reference attribute. |
#util.preprocessing_pipeline |
Textual preprocessing pass pipeline attached to a function-like operation. |
#util.uninitialized |
Storage attribute whose contents may be undefined at runtime. |
These types and attributes explain much of the dialect’s design. The dialect is concerned with object references, buffers, serialization, globals, initialization, and compiler/runtime metadata.
102.4 Operation Inventory
102.4.1 Type And Value Utilities
| Operation | What It Means |
|---|---|
util.null |
Produces a null value of a util reference-like type. |
util.cast |
Casts one util type to another, similar in spirit to a static or dynamic cast. |
util.cmp.eq |
Compares two values for equality. |
util.cmp.ne |
Compares two values for inequality. |
util.numeric.optional_narrow |
Records that a numeric narrowing is valid, but optional. Later passes may keep or remove the narrowing based on target policy. |
util.range.min |
Computes the minimum of range-like integer values. |
util.range.max |
Computes the maximum of range-like integer values. |
util.range.extents |
Computes the combined minimum and maximum extent of a set of ranges. |
util.align |
Aligns an offset up to a required power-of-two alignment. |
util.sizeof |
Returns the size in bytes of a datatype. |
util.switch |
A primitive value selection operation, useful before or during structural rewrites. |
102.4.2 Compiler Hints
| Operation | What It Means |
|---|---|
util.assume.int |
Binds integer assumptions, such as range or divisibility, to values. |
util.hoistable_conversion |
Defines a conversion between inputs and outputs that may be hoisted when the types and users allow it. |
util.optimization_barrier |
Prevents optimizations from crossing a value boundary. |
util.unfoldable_constant |
Represents a constant that should not be folded by the compiler. |
102.4.3 Structural Operations
| Operation | What It Means |
|---|---|
util.initializer |
A global initialization function. It is used to initialize module state and has ordering constraints with globals. |
util.func |
IREE’s function operation. It can carry IREE ABI and policy information beyond generic func.func. |
util.call |
Calls a util.func or imported callable symbol. It can carry tied operand metadata. |
util.return |
Returns from a util.func or util.initializer. |
util.unreachable |
Terminator that marks code as unreachable at runtime. |
util.scf.unreachable |
Non-terminator unreachable marker for use inside SCF regions. |
102.4.4 Globals
| Operation | What It Means |
|---|---|
util.global |
Declares a stateful global variable or constant. |
util.global.address |
Produces an indirect pointer-like reference to a global. |
util.global.load |
Loads a value directly from a named global. |
util.global.load.indirect |
Loads through an indirect global reference. |
util.global.store |
Stores a value directly into a named global. |
util.global.store.indirect |
Stores through an indirect global reference. |
102.4.5 Lists
| Operation | What It Means |
|---|---|
util.list.create |
Creates a new empty list. |
util.list.construct |
Constructs a list with initial values. |
util.list.size |
Returns the list size in elements. |
util.list.resize |
Resizes a list to a new element count. |
util.list.get |
Reads an element from a list. |
util.list.set |
Writes an element into a list. |
102.4.6 Buffers
| Operation | What It Means |
|---|---|
util.buffer.constant |
Creates a constant host-side byte buffer. |
util.buffer.alloc |
Allocates a buffer with undefined contents. |
util.buffer.dealloc |
Deallocates a buffer. |
util.buffer.slice |
Clones a subregion of a buffer. |
util.buffer.subspan |
Produces a reference to a subrange of a buffer. |
util.buffer.size |
Returns the total buffer storage size in bytes. |
util.buffer.storage |
Returns the underlying storage range for a buffer. |
util.buffer.copy |
Copies a byte range between buffers. |
util.buffer.compare |
Compares byte ranges from two buffers. |
util.buffer.fill |
Fills a byte range of a buffer with a value. |
util.buffer.load |
Loads a typed value from a byte buffer using buffer size, byte offset, and byte length. |
util.buffer.store |
Stores a typed value into a byte buffer using buffer size, byte offset, and byte length. |
util.buffer.hash |
Computes a hash over a byte range of a buffer. |
102.4.7 Strings And Status
| Operation | What It Means |
|---|---|
util.string.format |
Formats a string from a template and arguments. |
util.string.itoa |
Converts an integer to its decimal string representation. |
util.status.check_ok |
Raises a global failure if a status value is not OK. |
102.5 Transformations And Passes
| Pass | Role |
|---|---|
iree-util-apply-patterns |
Applies risky or IREE-specific canonicalization patterns. |
iree-util-attribute-call-graph |
Propagates call-related attributes from callees to call sites. |
iree-util-combine-initializers |
Combines global initializers into one initializer. |
iree-util-drop-compiler-hints |
Deletes compiler-only operations that have no runtime equivalent. Optionally keeps integer assumptions. |
iree-util-dump-module |
Writes the module to a textual or bytecode file path. |
iree-util-fixed-point-iterator |
Runs a nested pass pipeline until it reaches a fixed point. |
iree-util-ipo |
Performs basic inter-procedural optimization. |
iree-util-lift-cfg-to-scf |
Converts reducible unstructured CFG in util.initializer and util.func into structured SCF operations. |
iree-util-link-modules |
Links external function declarations from explicitly provided modules or library search paths. |
iree-util-optimize-int-arithmetic |
Optimizes integer arithmetic using dataflow analysis and rewrite patterns. |
iree-util-propagate-subranges |
Propagates resource subranges across the program. |
iree-util-strip-and-splat-constants |
Replaces constant globals with splat forms to reduce stored data. |
iree-util-strip-debug-ops |
Removes debug-only operations. |
iree-util-verify-initialization-order |
Verifies initializer and global mutation ordering constraints. |
iree-util-verify-structured-control-flow |
Ensures function-like operations contain no unstructured branch operations. |
iree-util-fold-globals |
Folds duplicate globals and propagates constants. |
iree-util-fuse-globals |
Fuses correlated globals together. |
iree-util-hoist-into-globals |
Hoists eligible constant expressions into globals, subject to a size-increase threshold. |
iree-util-simplify-global-accesses |
Hoists loads and sinks stores to shrink data dependency regions around variables. |
iree-util-import-resources |
Converts large inline dense attributes to resource-backed attributes that IREE can manage efficiently. |
iree-util-annotate-op-ordinals |
Adds globally unique IDs to operations for debugging. |
iree-util-test-conversion |
Tests util dialect conversion patterns. |
iree-util-test-float-range-analysis |
Tests floating-point range analysis. |
iree-util-test-integer-divisibility-analysis |
Tests integer divisibility analysis. |
102.6 Conversion Paths
The util conversion helpers are as important as the operations themselves.
populateFuncToUtilPatterns converts top-level func.func, func.call, and func.return into util.func, util.call, and util.return. During this conversion, IREE can preserve selected attributes such as reflection, stream affinity, VM interop metadata, versioning, side-effect information, and argument/result attributes. Generic noinline is translated into the util inlining policy #util.inline.never.
populateMemRefToUtilPatterns converts rank-0 and rank-1 identity-layout memrefs into !util.buffer by default, or into a caller-specified buffer type for multi-stage lowerings. memref.global becomes util.global plus a util.initializer that stores a util.buffer.constant. memref.get_global becomes util.global.load. memref.alloca becomes util.buffer.alloc. memref.load and memref.store become util.buffer.load and util.buffer.store with byte offsets computed from element size and indices.
populateUtilConversionPatterns updates util operations and nested util types under a type converter. It knows how to convert !util.ptr<T> by converting the target type, and !util.list<T> by converting the element type. It also provides generic conversion patterns for operations such as util.assume.int, util.optimization_barrier, util.list.create, util.list.get, and util.list.set.
populateGenericStructuralConversionPatterns rewrites structural operations in util, func, cf, arith, and scf when type conversion changes their operand, result, or region types. This is why util is common in cross-dialect conversion: it helps keep structure valid while types are changing.
102.7 How To Read Util IR
Start with the module-level structure. Look for util.global and util.initializer first. They tell you what persistent state exists and how that state is initialized. If initialization-order verification fails, the problem is usually an initializer touching a global too early, writing an immutable global more than once, or mutating a global that should only receive its initial value.
Next, read callable structure. util.func and util.call behave like ordinary functions and calls, but they often carry ABI, inlining, reflection, tied operand, or VM interop metadata. Those attributes are often more important than the body when you are debugging import or runtime boundary behavior.
Then inspect globals and resources. util.global.load, util.global.store, util.global.address, and their indirect forms are the access points. If constants are large, iree-util-import-resources, iree-util-hoist-into-globals, iree-util-fold-globals, and iree-util-fuse-globals may change where data lives without changing the visible program result.
Finally, read buffers as byte-addressed storage. util.buffer.load and util.buffer.store are not high-level tensor operations. They are byte-buffer operations with explicit sizes and offsets. If you see them after memref conversion, the original typed memory abstraction has already been lowered.
102.8 What It Implies
Seeing util usually means the compiler is crossing an infrastructure boundary. It may be importing source IR into IREE, preserving ABI metadata, lowering generic memory into runtime buffers, moving constants into globals, or preparing module state for later serialization.
The implication for beginners is that util is less about one execution model and more about IREE’s shared contracts. The structural ops define how IREE modules call and initialize. The global ops define how state is stored and accessed. The buffer ops define how byte storage is represented. The hint ops tell optimizers what they may assume or must avoid. The passes keep those contracts consistent while other dialects do the domain-specific work.