yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Jay KwakUpdate document to include lua build step (#7886)f9f23cbd9

master
10.1 KiB306 linesraw

Slang IR Instruction Management and Versioning

This document explains how Slang's intermediate representation (IR) instructions are defined, generated, and versioned. It covers the workflow for adding or modifying instructions and the mechanisms that ensure backwards compatibility for serialized IR modules.

High-Level Concepts

The Slang IR uses a code generation approach where instruction definitions are centralized in a Lua file (slang-ir-insts.lua), and various C++ headers and source files are generated from this single source of truth. This ensures consistency across the codebase and enables sophisticated features like backwards compatibility through stable instruction naming.

Key Components

  • Instruction Definitions (slang-ir-insts.lua): The canonical source for all IR instruction definitions
  • Stable Names (slang-ir-insts-stable-names.lua): Maps instruction names to permanent integer IDs for backwards compatibility
  • Code Generation (via Fiddle): Generates C++ enums, structs, and tables from the Lua definitions
  • Module Versioning: Tracks compatibility ranges for serialized IR modules

The Instruction Definition System

Source of Truth: slang-ir-insts.lua

All IR instructions are defined in source/slang/slang-ir-insts.lua. This file contains a hierarchical table structure that defines:

  • Instruction names and their organization into categories
  • Struct names for the C++ representation (if different from the default)
  • Flags like hoistable, parent, global, etc.
  • (Optionally) Minimum operand counts
  • (Optionally) The operands themselves
  • Parent-child relationships in the instruction hierarchy

Here's a simplified example of how instructions are defined:

local insts = {
    { nop = {} },
    {
        Type = {
            {
                BasicType = {
                    hoistable = true,
                    { Void = { struct_name = "VoidType" } },
                    { Bool = { struct_name = "BoolType" } },
                    { Int = { struct_name = "IntType" } },
                    -- ... more basic types
                },
            },
            -- ... more type categories
        },
    },
    -- ... more instruction categories
}

The hierarchy is important: instructions inherit properties from their parent categories. For example, all BasicType instructions inherit the hoistable = true flag.

Code Generation Flow

The Fiddle tool processes slang-ir-insts.lua and generates several outputs:

  1. Enum Definitions (slang-ir-insts-enum.h):

    • IROp enum with values like kIROp_Void, kIROp_Bool, etc.
    • Range markers like kIROp_FirstBasicType and kIROp_LastBasicType
  2. Struct Definitions (slang-ir-insts.h):

    • C++ struct definitions for instruction types not manually defined
    • leafInst() and baseInst() macros for RTTI support
    • If operands of an IR are specified in slang-ir-insts.lua in the format { { "operand1_name", "operand1_type" }, {"operand2_name"} } and so on, Fiddle will generate getters for each of the operands as part of the IR's struct. Note that the order in which the operands are listed matters and specification of the type of the operand is optional; defaulting to "IRInst" when the type is not specified.
  3. Instruction Info Table (slang-ir-insts-info.cpp):

    • Maps opcodes to their string names, operand counts, and flags
    • Used for debugging, printing, and validation
  4. Stable Name Mappings (slang-ir-insts-stable-names.cpp):

    • Bidirectional mapping between opcodes and stable IDs
    • Critical for backwards compatibility

Adding or Modifying Instructions

Adding a New Instruction

To add a new IR instruction:

  1. Edit slang-ir-insts.lua: Add your instruction in the appropriate category:

    { MyNewInst = { min_operands = 2 } },
  2. Run the build: The build system will automatically regenerate the C++ files.

  3. Update the stable names: Either

    • Run the validation script:

      Note: Skip make command if lua is already built.

      make -C external/lua MYCFLAGS="-DLUA_USE_POSIX" MYLIBS=""
      ./external/lua/lua extras/check-ir-stable-names.lua update
    • Or add a new ID to the mapping in source/slang/slang-ir-insts-stable-names.lua, this is checked for consistency in CI so it's safe to add manually.

    This assigns a permanent ID to your new instruction.

  4. Implement the instruction logic: Add handling in relevant files like:

    • slang-ir-insts.h (if you need a custom struct definition)
    • slang-emit-*.cpp files for code generation
    • slang-ir-lower-*.cpp files for transformations
  5. Update the module version: In slang-ir.h, increment k_maxSupportedModuleVersion:

    const static UInt k_maxSupportedModuleVersion = 1; // was 0

Modifying an Existing Instruction

Modifications require more care:

  • Adding operands or changing semantics: This is a breaking change. You must:

    1. Increment both k_minSupportedModuleVersion and k_maxSupportedModuleVersion
    2. Document the change in the version history
  • Renaming: Don't rename instructions directly. Instead:

    1. Add the new instruction
    2. Mark the old one as deprecated
    3. Eventually remove it in a major version bump

The Stable Name System

Purpose

When Slang serializes IR modules, it needs to handle the case where the compiler version that reads a module is different from the one that wrote it. Instructions might have been added, removed, or reordered in the IROp enum.

The stable name system solves this by assigning permanent integer IDs to each instruction. These IDs never change once assigned.

How It Works

  1. Assignment: When a new instruction is added, the check-ir-stable-names.lua script assigns it the next available ID.

  2. Serialization: When writing a module, opcodes are converted to stable IDs:

    auto stableName = getOpcodeStableName(value);
  3. Deserialization: When reading, stable IDs are converted back:

    value = getStableNameOpcode(stableName);
  4. Validation: The CI system ensures the stable name table stays synchronized with the instruction definitions.

Maintenance

The stable name table is validated in CI:

./extras/check-ir-stable-names-gh-actions.sh

This script:

  • Verifies all instructions have stable names
  • Checks for duplicate IDs
  • Ensures the mapping is bijective
  • Can automatically fix missing entries

Module Versioning

Version Types

Slang tracks two version numbers:

  1. Module Version (IRModule::m_version): The semantic version of the IR instruction set

    • Range: k_minSupportedModuleVersion to k_maxSupportedModuleVersion
    • Stored in each serialized module
  2. Serialization Version (IRModuleInfo::serializationVersion): The format version

    • Allows changes to how data is encoded

When to Update Versions

Minor Version Bump (increment k_maxSupportedModuleVersion only):

  • Adding new instructions
  • Adding new instruction flags that don't affect existing code
  • Adding new optional operands

Major Version Bump (increment both min and max):

  • Removing instructions
  • Changing instruction semantics
  • Modifying minimum operand counts or types
  • Any change that breaks compatibility

Version Checking

During deserialization:

if (fossilizedModuleInfo->serializationVersion != IRModuleInfo::kSupportedSerializationVersion)
    return SLANG_FAIL;

// Later, after loading instructions:
if (hasUnrecognizedInsts)
    return SLANG_FAIL;

Serialization Details

The Flat Representation

For efficiency, IR modules are serialized as a "flat" representation:

struct FlatInstTable
{
    List<InstAllocInfo> instAllocInfo;  // Op + operand count
    List<Int64> childCounts;            // Children per instruction
    List<SourceLoc> sourceLocs;         // Source locations
    List<Int64> operandIndices;         // Flattened operand references
    List<Int64> stringLengths;          // For string/blob constants
    List<uint8_t> stringChars;          // Concatenated string data
    List<UInt64> literals;              // Integer/float constant values
};

This representation:

  • Minimizes pointer chasing during deserialization
  • Groups similar data together for better cache performance
  • Enables efficient bulk operations

Traversal Order

Instructions are serialized in a specific order for performance:

traverseInstsInSerializationOrder(moduleInst, [&](IRInst* inst) {
    // Process instruction
});

The traversal:

  1. Visits instructions in preorder (parent before children)
  2. Optionally reorders module-level instructions to group constants together
  3. Maintains deterministic ordering for reproducible builds

Debugging and Validation

Available Tools

  1. Module Info Inspection:

    slangc -get-module-info module.slang-module

    Shows module name, version, and compiler version.

  2. Version Query:

    slangc -get-supported-module-versions

    Reports the supported version range.

  3. IR Dumping:

    slangc -dump-ir module.slang

    Shows the IR in human-readable form.

Common Issues

"Unrecognized instruction" errors: The module contains instructions unknown to this compiler version. Update Slang or recompile the module.

Stable name validation failures: Run the update script and commit the changes:

Note: Skip make command if lua is already built.

make -C external/lua MYCFLAGS="-DLUA_USE_POSIX" MYLIBS=""
./external/lua/lua extras/check-ir-stable-names.lua update

Version mismatch: The module was compiled with an incompatible Slang version. Check the version ranges and recompile if necessary.

Best Practices

  1. Always update stable names: After adding instructions, run the validation script before committing.

  2. Document version changes: When bumping module versions, add a comment explaining what changed.

  3. Prefer addition over modification: When possible, add new instructions rather than changing existing ones.

  4. Group related changes: If making multiple breaking changes, do them together in a single version bump.

1# Slang IR Instruction Management and Versioning
2
3This document explains how Slang's intermediate representation (IR) instructions are defined, generated, and versioned. It covers the workflow for adding or modifying instructions and the mechanisms that ensure backwards compatibility for serialized IR modules.
4
5## High-Level Concepts
6
7The Slang IR uses a code generation approach where instruction definitions are centralized in a Lua file (`slang-ir-insts.lua`), and various C++ headers and source files are generated from this single source of truth. This ensures consistency across the codebase and enables sophisticated features like backwards compatibility through stable instruction naming.
8
9### Key Components
10
11- **Instruction Definitions** (`slang-ir-insts.lua`): The canonical source for all IR instruction definitions
12- **Stable Names** (`slang-ir-insts-stable-names.lua`): Maps instruction names to permanent integer IDs for backwards compatibility
13- **Code Generation** (via Fiddle): Generates C++ enums, structs, and tables from the Lua definitions
14- **Module Versioning**: Tracks compatibility ranges for serialized IR modules
15
16## The Instruction Definition System
17
18### Source of Truth: `slang-ir-insts.lua`
19
20All IR instructions are defined in `source/slang/slang-ir-insts.lua`. This file contains a hierarchical table structure that defines:
21
22- Instruction names and their organization into categories
23- Struct names for the C++ representation (if different from the default)
24- Flags like `hoistable`, `parent`, `global`, etc.
25- (Optionally) Minimum operand counts
26- (Optionally) The operands themselves
27- Parent-child relationships in the instruction hierarchy
28
29Here's a simplified example of how instructions are defined:
30
31```lua
32local insts = {
33    { nop = {} },
34    {
35        Type = {
36            {
37                BasicType = {
38                    hoistable = true,
39                    { Void = { struct_name = "VoidType" } },
40                    { Bool = { struct_name = "BoolType" } },
41                    { Int = { struct_name = "IntType" } },
42                    -- ... more basic types
43                },
44            },
45            -- ... more type categories
46        },
47    },
48    -- ... more instruction categories
49}
50```
51
52The hierarchy is important: instructions inherit properties from their parent categories. For example, all `BasicType` instructions inherit the `hoistable = true` flag.
53
54### Code Generation Flow
55
56The Fiddle tool processes `slang-ir-insts.lua` and generates several outputs:
57
581. **Enum Definitions** (`slang-ir-insts-enum.h`):
59
60   - `IROp` enum with values like `kIROp_Void`, `kIROp_Bool`, etc.
61   - Range markers like `kIROp_FirstBasicType` and `kIROp_LastBasicType`
62
632. **Struct Definitions** (`slang-ir-insts.h`):
64
65   - C++ struct definitions for instruction types not manually defined
66   - `leafInst()` and `baseInst()` macros for RTTI support
67   - If operands of an IR are specified in `slang-ir-insts.lua` in the format `{ { "operand1_name", "operand1_type" }, {"operand2_name"} }` and so on,
68     Fiddle will generate getters for each of the operands as part of the IR's struct. Note that the order in which the operands are listed matters and
69     specification of the type of the operand is optional; defaulting to "IRInst" when the type is not specified.
70
713. **Instruction Info Table** (`slang-ir-insts-info.cpp`):
72
73   - Maps opcodes to their string names, operand counts, and flags
74   - Used for debugging, printing, and validation
75
764. **Stable Name Mappings** (`slang-ir-insts-stable-names.cpp`):
77   - Bidirectional mapping between opcodes and stable IDs
78   - Critical for backwards compatibility
79
80## Adding or Modifying Instructions
81
82### Adding a New Instruction
83
84To add a new IR instruction:
85
861. **Edit `slang-ir-insts.lua`**: Add your instruction in the appropriate category:
87
88   ```lua
89   { MyNewInst = { min_operands = 2 } },
90   ```
91
922. **Run the build**: The build system will automatically regenerate the C++ files.
93
943. **Update the stable names**: Either
95
96   - Run the validation script:
97
98     **Note**: Skip make command if lua is already built.
99     ```bash
100     make -C external/lua MYCFLAGS="-DLUA_USE_POSIX" MYLIBS=""
101     ./external/lua/lua extras/check-ir-stable-names.lua update
102     ```
103
104   - Or add a new ID to the mapping in `source/slang/slang-ir-insts-stable-names.lua`, this is checked for consistency in CI so it's safe to add manually.
105
106   This assigns a permanent ID to your new instruction.
107
1084. **Implement the instruction logic**: Add handling in relevant files like:
109
110   - `slang-ir-insts.h` (if you need a custom struct definition)
111   - `slang-emit-*.cpp` files for code generation
112   - `slang-ir-lower-*.cpp` files for transformations
113
1145. **Update the module version**: In `slang-ir.h`, increment `k_maxSupportedModuleVersion`:
115   ```cpp
116   const static UInt k_maxSupportedModuleVersion = 1; // was 0
117   ```
118
119### Modifying an Existing Instruction
120
121Modifications require more care:
122
123- **Adding operands or changing semantics**: This is a breaking change. You must:
124
125  1. Increment both `k_minSupportedModuleVersion` and `k_maxSupportedModuleVersion`
126  2. Document the change in the version history
127
128- **Renaming**: Don't rename instructions directly. Instead:
129
130  1. Add the new instruction
131  2. Mark the old one as deprecated
132  3. Eventually remove it in a major version bump
133
134## The Stable Name System
135
136### Purpose
137
138When Slang serializes IR modules, it needs to handle the case where the compiler version that reads a module is different from the one that wrote it. Instructions might have been added, removed, or reordered in the `IROp` enum.
139
140The stable name system solves this by assigning permanent integer IDs to each instruction. These IDs never change once assigned.
141
142### How It Works
143
1441. **Assignment**: When a new instruction is added, the `check-ir-stable-names.lua` script assigns it the next available ID.
145
1462. **Serialization**: When writing a module, opcodes are converted to stable IDs:
147
148   ```cpp
149   auto stableName = getOpcodeStableName(value);
150   ```
151
1523. **Deserialization**: When reading, stable IDs are converted back:
153
154   ```cpp
155   value = getStableNameOpcode(stableName);
156   ```
157
1584. **Validation**: The CI system ensures the stable name table stays synchronized with the instruction definitions.
159
160### Maintenance
161
162The stable name table is validated in CI:
163
164```bash
165./extras/check-ir-stable-names-gh-actions.sh
166```
167
168This script:
169
170- Verifies all instructions have stable names
171- Checks for duplicate IDs
172- Ensures the mapping is bijective
173- Can automatically fix missing entries
174
175## Module Versioning
176
177### Version Types
178
179Slang tracks two version numbers:
180
1811. **Module Version** (`IRModule::m_version`): The semantic version of the IR instruction set
182
183   - Range: `k_minSupportedModuleVersion` to `k_maxSupportedModuleVersion`
184   - Stored in each serialized module
185
1862. **Serialization Version** (`IRModuleInfo::serializationVersion`): The format version
187   - Allows changes to how data is encoded
188
189### When to Update Versions
190
191**Minor Version Bump** (increment `k_maxSupportedModuleVersion` only):
192
193- Adding new instructions
194- Adding new instruction flags that don't affect existing code
195- Adding new optional operands
196
197**Major Version Bump** (increment both min and max):
198
199- Removing instructions
200- Changing instruction semantics
201- Modifying minimum operand counts or types
202- Any change that breaks compatibility
203
204### Version Checking
205
206During deserialization:
207
208```cpp
209if (fossilizedModuleInfo->serializationVersion != IRModuleInfo::kSupportedSerializationVersion)
210    return SLANG_FAIL;
211
212// Later, after loading instructions:
213if (hasUnrecognizedInsts)
214    return SLANG_FAIL;
215```
216
217## Serialization Details
218
219### The Flat Representation
220
221For efficiency, IR modules are serialized as a "flat" representation:
222
223```cpp
224struct FlatInstTable
225{
226    List<InstAllocInfo> instAllocInfo;  // Op + operand count
227    List<Int64> childCounts;            // Children per instruction
228    List<SourceLoc> sourceLocs;         // Source locations
229    List<Int64> operandIndices;         // Flattened operand references
230    List<Int64> stringLengths;          // For string/blob constants
231    List<uint8_t> stringChars;          // Concatenated string data
232    List<UInt64> literals;              // Integer/float constant values
233};
234```
235
236This representation:
237
238- Minimizes pointer chasing during deserialization
239- Groups similar data together for better cache performance
240- Enables efficient bulk operations
241
242### Traversal Order
243
244Instructions are serialized in a specific order for performance:
245
246```cpp
247traverseInstsInSerializationOrder(moduleInst, [&](IRInst* inst) {
248    // Process instruction
249});
250```
251
252The traversal:
253
2541. Visits instructions in preorder (parent before children)
2552. Optionally reorders module-level instructions to group constants together
2563. Maintains deterministic ordering for reproducible builds
257
258## Debugging and Validation
259
260### Available Tools
261
2621. **Module Info Inspection**:
263
264   ```bash
265   slangc -get-module-info module.slang-module
266   ```
267
268   Shows module name, version, and compiler version.
269
2702. **Version Query**:
271
272   ```bash
273   slangc -get-supported-module-versions
274   ```
275
276   Reports the supported version range.
277
2783. **IR Dumping**:
279   ```bash
280   slangc -dump-ir module.slang
281   ```
282   Shows the IR in human-readable form.
283
284### Common Issues
285
286**"Unrecognized instruction" errors**: The module contains instructions unknown to this compiler version. Update Slang or recompile the module.
287
288**Stable name validation failures**: Run the update script and commit the changes:
289
290**Note**: Skip make command if lua is already built.
291```bash
292make -C external/lua MYCFLAGS="-DLUA_USE_POSIX" MYLIBS=""
293./external/lua/lua extras/check-ir-stable-names.lua update
294```
295
296**Version mismatch**: The module was compiled with an incompatible Slang version. Check the version ranges and recompile if necessary.
297
298## Best Practices
299
3001. **Always update stable names**: After adding instructions, run the validation script before committing.
301
3022. **Document version changes**: When bumping module versions, add a comment explaining what changed.
303
3043. **Prefer addition over modification**: When possible, add new instructions rather than changing existing ones.
305
3064. **Group related changes**: If making multiple breaking changes, do them together in a single version bump.