Introduction

What is NIR?

NIR is an Intermediate Representation (IR) that’s designed for the needs of graphics drivers in Mesa. It sits between a frontend that translates another language or IR such as GLSL IR or TGSI to NIR and the driver’s own backend IR. It includes several optimization passes, as well as some features that are useful for making translating to the driver’s own IR easier, although it is not intended to replace the backend and as such doesn’t support backend-specific tasks such as scheduling, register allocation, etc.

NIR was designed with several goals in mind:

  • To be backend-agnostic. There are some features, such as registers, that require some level of backend involvement, but no core NIR optimization pass depends on the semantics of these features. Instead, almost all interaction besides simple operations such as addition and subtraction is described using loads and stores to variables that are similar to variables in GLSL.
  • To natively understand constructs, such as structured control flow, that are common in GPU’s but not elsewhere.
  • To be compatible with the extensive body of literature around compiler technology. NIR natively supports Single Static Assignment (SSA), which is a prerequisite for many important optimizations, and all of its optimizations assume SSA. Furthermore, NIR structures programs into basic blocks, which is often assumed by compiler papers, and it supports several analyses such as dominance analysis and liveness analysis that are often used by optimizations. All of these things greatly reduce the hassle of translating an idea described in a paper into code.

One thing that NIR is not designed to be is a library for users outside of Mesa. It’s not possible to extend NIR at run-time to add additional operations, although it’s flexible enough that it’s usually easy to do at compile time. Furthermore, there is no stable API; it’s expected that producers and consumers will live in-tree so we can update them if we have to make breaking changes.

Organization

NIR is written in C, although in a very object-oriented manner. Structures and enums are typedef’ed:

typedef struct nir_foo {
    /* stuff */
} nir_foo;

typedef enum {
    nir_enum_thing1,
    nir_enum_thing2,
    nir_enum_thing3,
} nir_enum;

and inheritance is done through embedding structures. Upcasting is done through inline functions defined by the NIR_DEFINE_CAST macro. For example, here’s how an animal structure inherited by cows, cats, and dogs would be defined:

typedef enum {
    nir_animal_type_cow,
    nir_animal_type_dog,
    nir_animal_type_cat,
} nir_animal_type;

typedef struct {
    nir_animal_type type;
    /* stuff */
} nir_animal;

typedef struct {
    nir_animal animal;
} nir_cow;

typedef struct {
    nir_animal animal;
} nir_dog;

typedef struct {
    nir_animal animal;
} nir_cat;

NIR_DEFINE_CAST(nir_animal_as_cow, nir_animal, nir_cow, animal)
NIR_DEFINE_CAST(nir_animal_as_dog, nir_animal, nir_dog, animal)
NIR_DEFINE_CAST(nir_animal_as_cat, nir_animal, nir_cat, animal)

Datastructures

The core IR consists of various structures defined in nir.h, as well as functions for creating, destroying, and manipulating them. Currently, these structures include:

  • nir_shader: represents a linked or unlinked shader. This may contain one or more functions as well as global registers and variables and other whole-shader type information. Right now, a nir_shader usually only contains one function called “main”, but that may change.
  • nir_function: represents a GLSL-style overloaded function, for linking purposes. It includes the name as well as a list of overloads.
  • nir_function_overload: represents a declaration or definition of a function overload. If it’s a declaration, then the impl field will be NULL, and if it’s a definition then impl will point to a nir_function_impl.
  • nir_function_impl: contains function-local stuff such as local variables and registers. It’s also the root of the control flow tree.
  • nir_cf_node: represents a node in the control flow tree. For more information, see Control Flow.
    • nir_if
    • nir_loop
    • nir_block
  • nir_instr: the base class for instructions in NIR. Each nir_block has a list of nir_instr‘s. For more information, see Instructions.
    • nir_alu_instr
    • nir_call_instr
    • nir_jump_instr
    • nir_tex_instr
    • nir_intrinsic_instr
    • nir_load_const_instr
    • nir_ssa_undef_instr
    • nir_phi_instr
    • nir_parallel_copy_instr
  • nir_dest
  • nir_src
  • nir_ssa_def
  • nir_register
  • nir_variable
  • nir_deref * nir_deref_var * nir_deref_struct * nir_deref_array

Printing

NIR includes a function called nir_print_shader() for printing the contents of a shader to a given FILE *, which can be useful for debugging. In addition, nir_print_instr() is exposed, which can be useful for examining instructions in the debugger.

Validation

There are various bits of redundant information as well as various invariants which must be satisfied in the IR. Often, passes will have bugs which result in those invariants being broken or the information left incorrect, which may only blow up much later when some other pass or analysis relies on that information. To make debugging those sorts of problems much easier, NIR has a validation pass, nir_validate_shader(), which makes sure that the shader is valid. It’s a no-op on release builds, but when debugging it catches many bugs at the source instead of much later.

Table Of Contents

Previous topic

NIR Documentation

Next topic

Control Flow

This Page