Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

14 KiB

How to Add a New Instruction

This guide shows you how to add a new instruction from bottom (Assembly IR) to top (Logical IR).

Note: Python API is decoupled via a separate wrapper layer and is not directly affected by these changes.

Instruction definitions and costs use the DEF_T system: they live in hardware/src/gfx/GfxXXX/GfxXXXInstructions.def (and optionally GfxXXXFormats.def in the same folder), using DEF_T for standalone instructions or DEF_BATCH for groups sharing a format. Tablegen generates *_init.inc, *_costs.inc, and *_operands.inc; the architecture .cpp/.hpp files only include those. Do not add DEF_T or cost tables manually in the .cpp.


Quick checklist: adding an instruction (as few edits as possible)

Goal What to do
Assembly-only instruction Add DEF_T(Class, "mnemonic", .format = X, .flags = {...}) or an entry inside a DEF_BATCH in hardware/src/gfx/GfxXXX/GfxXXXInstructions.def. Rebuild.
Group of related instructions Use DEF_BATCH(.format = X, ...) to share the format across entries. Each entry is Class, "mnemonic" with optional per-entry .flags, .cost, .operand_fields, .logical.
Used from Logical IR / Rocisa Add .logical = "LogicalName" to the DEF_T / DEF_BATCH entry. Tablegen auto-generates the setGfxXXXLogicalToArchMap() — no manual .cpp edit needed. Also add an entry in tools/tablegen/LogicalInstructionDefs.inc.
Non-default cost Add .cost = {cycle, latency} to the DEF_T or DEF_BATCH entry. No .cpp edit.
Modifier-dependent cost (e.g. matrix fmt) Add .costOverride = { { MatrixFmtModifiers(FP4, FP4), cycle, latency } } alongside .cost.
Operand requirements (e.g. 4 SGPRs, 8 SGPRs) Define .fields in the format (e.g., DEF_FORMAT(TENSOR, .fields = { {S0, ..., sgpr, 128}, {S1, ..., sgpr, 256} })) or override per-instruction via .operand_fields = { {D0, .size=64} }. Tablegen generates *_operands.inc. No manual .hpp edit.
Dual-encoding operand override Add .alt_operand_fields = { ... } for the promoted encoding (e.g. VOP3 form of a VOP2 instruction).
New flag or format Add flag in include/stinkytofu/hardware/Flags.def and/or format in hardware/src/gfx/GfxXXX/GfxXXXFormats.def; then use in DEF_T / DEF_BATCH.
New instruction flag Add flag in include/stinkytofu/hardware/Flags.def and use in DEF_T / DEF_BATCH .flags.

Single place to add a normal instruction: hardware/src/gfx/GfxXXX/GfxXXXInstructions.def — one DEF_T or one entry in an existing DEF_BATCH. Rebuild. If it must be visible to Logical IR, add .logical = "Name" in the same .def entry and add the Logical def in LogicalInstructionDefs.inc.


Chapter 1: Assembly IR (Hardware Layer)

To add a new StinkyTofu assembly IR, you'll need to access the following files:

include/stinkytofu/hardware/Flags.def                  # Instruction flags (optional)
hardware/src/gfx/GfxXXX/GfxXXXInstructions.def   # Instruction defs + costs + operands (DEF_T / DEF_BATCH)
hardware/src/gfx/GfxXXX/GfxXXXFormats.def        # Format definitions (if new format needed)
hardware/src/gfx/GfxXXX/GfxXXX.cpp               # Rocisa maps (only if not using .logical in .def)

Here we will add the instruction s_wait_tensorcnt step by step.

Step 1. Add a flag (Optional)

Add a flag in include/stinkytofu/hardware/Flags.def for a new instruction type if needed.

MACRO(IF_WaitTensorCnt)

Step 2. Use the flag in DEF_T (Optional)

If you added a new flag in Step 1, use it in the instruction's .flags in the .def file. Tablegen will emit DEF_T("mnemonic", IF_WaitTensorCnt) in the generated _init.inc. No separate struct needed.

Note: If your instruction is commutative (e.g., v_add_f32 where a+b = b+a), mark it as such:

struct FloatAddInst : GfxInstDef
{
    FloatAddInst()
    {
        hwInstDesc.flags.set(IF_Commutative);  // Allow operand swapping in optimizer
    }
};

Step 3. Add definition and optional cost in the architecture .def file

s_wait_tensorcnt is a Gfx1250 feature. Add it to hardware/src/gfx/Gfx1250/Gfx1250Instructions.def (not in the .cpp).

Use DEF_T(ClassName, "mnemonic", ...) or add an entry inside a DEF_BATCH(...). Both accept the same optional fields: .format, .flags, .cost, .logical, .operand_fields. The tablegen generates the init and cost tables; the .cpp only includes the generated files.

DEF_T — single instruction

Example (minimal — uses arch default cost). Use a flag that exists in include/stinkytofu/hardware/Flags.def (without the IF_ prefix, e.g. WaitTensorCnt):

DEF_T(SWaitTensorcntInst, "s_wait_tensorcnt",
    .format = SOPP,
    .flags = {WaitTensorCnt},
    .logical = "SWaitTensorcnt"
)

Example with explicit cost (cycle, latency):

DEF_T(SWaitTensorcntInst, "s_wait_tensorcnt",
    .format = SOPP,
    .flags = {WaitTensorCnt},
    .cost = {1, 1},
    .logical = "SWaitTensorcnt"
)

DEF_BATCH — group of instructions sharing a format

When several instructions share the same format, use DEF_BATCH to avoid repeating .format on every entry. Shared header fields (.format, .flags, etc.) are inherited by all entries; per-entry fields override or extend them (flags are additive).

DEF_BATCH(.format = SOPP_WAIT16,
    SWaitLoadcntInst, "s_wait_loadcnt", .flags = {WaitCnt},
    SWaitStorecntInst, "s_wait_storecnt", .flags = {WaitCnt},
    SWaitTensorcntInst, "s_wait_tensorcnt", .flags = {WaitTensorCnt}, .logical = "SWaitTensorcnt",
    SWaitAluInst, "s_wait_alu", .flags = {HasSideEffect},
        .operand_fields = { {S0, .type=wait_alu} },
)

Each entry is: ClassName, "mnemonic" followed by optional per-entry fields (.flags, .cost, .logical, .operand_fields, .costOverride). Entries must not redefine .format — that comes from the batch header.

Real-world example with load instructions, costs, and operand overrides:

DEF_BATCH(.format = MUBUF_LOAD,
    BufferLoadU8Inst, "buffer_load_u8", .cost = {12, 108}, .logical = "BufferLoadU8",
    BufferLoadB32Inst, "buffer_load_b32", .cost = {12, 108}, .logical = "BufferLoadB32",
    BufferLoadB128Inst, "buffer_load_b128", .cost = {12, 116}, .logical = "BufferLoadB128",
        .operand_fields = { {D0, .size=128} },
)

Then rebuild so tablegen runs (e.g. cmake --build .). See Architecture Overview for full syntax and format inheritance.

Step 4. Rocisa LogicalToArch mapping (automatic via .logical)

If you added .logical = "LogicalName" to the DEF_T or DEF_BATCH entry in Step 3, the tablegen auto-generates the setGfxXXXLogicalToArchMap() function. No manual .cpp edit is needed.

Multiple logical names can map to the same mnemonic:

SMovB32Inst, "s_mov_b32", .logical = {"SMovB32", "SSetMask"},

If you do not use .logical in the .def, you can still add the mapping manually in hardware/src/gfx/Gfx1250/Gfx1250.cpp inside setGfx1250LogicalToArchMap():

{"SWaitTensorcnt", "s_wait_tensorcnt"},

Prefer .logical in the .def — it keeps the mapping next to the definition.


Chapter 2: Logical IR (High-Level IR Layer)

Step 5: Add Logical IR Definition

Add to shared/stinkytofu/tools/tablegen/LogicalInstructionDefs.inc:

{"SWaitTensorcnt",     // className
 "s_wait_tensorcnt",   // mnemonic (must match assembly)
 "Wait for tensor operations to complete",  // comment
 0,                    // numSrcs (no source operands)
 false,                // hasDest (no destination)
 "Synchronization",    // category
 false,                // supportsDPP
 false,                // supportsSDWA
 false,                // hasDS
 false},               // isCommutative

Step 6: Add Hardware Metadata (Costs + Operand Requirements)

6a. Instruction Costs

Costs are defined in the architecture .def file and arch.cmake, not in the .cpp.

  • Default cost: Each arch sets a default in arch.cmake (ARCH_DEFAULT_CYCLE, ARCH_DEFAULT_LATENCY). E.g. Gfx1250 uses 1,1. Instructions without .cost in the .def use that default.
  • Override: In hardware/src/gfx/GfxXXX/GfxXXXInstructions.def, add .cost = {cycle, latency} to the instruction's DEF_T(...) or DEF_BATCH entry. Tablegen emits only non-default costs into GfxXXX_costs.inc; the generated block applies them.

Example — standalone DEF_T with cost:

DEF_T(WaitTensorCntInst, "s_wait_tensorcnt",
    .format = SOPP,
    .flags = {SALU},
    .cost = {1, 1}
)

Example — inside a DEF_BATCH:

DEF_BATCH(.format = MUBUF_LOAD,
    BufferLoadU8Inst, "buffer_load_u8", .cost = {12, 108}, .logical = "BufferLoadU8",
    BufferLoadB32Inst, "buffer_load_b32", .cost = {12, 108}, .logical = "BufferLoadB32",
)
Modifier-dependent cost overrides (.costOverride)

Matrix instructions can have different costs depending on the data format. Use .costOverride to specify alternate {cycle, latency} for specific format combinations:

DEF_T(VWmmaF3216x16x128F8f6f4Inst, "v_wmma_f32_16x16x128_f8f6f4",
    .format = WMMA,
    .cost = {5, 8},
    .costOverride = { { MatrixFmtModifiers(FP4, FP4), 3, 4 } },
    .operand_fields = { {S0, .size=512}, {S1, .size=512} }
)

Inside a DEF_BATCH:

DEF_BATCH(.format = MXWMMA_SCALE16,
    VWmmaScale16F3216x16x128F8f6f4Inst, "v_wmma_scale16_f32_16x16x128_f8f6f4",
        .cost = {6, 8}, .costOverride = { { MatrixFmtModifiers(FP4, FP4), 4, 4 } },
)

Do not add cost entries to any array in GfxXXX.cpp; they are generated from the .def. See Architecture Overview.

6b. Add Operand Requirements (Optional)

If your instruction has specific register width or type requirements (e.g., tensor_load_to_lds requires 4 SGPRs for src0, 8 SGPRs for src1), define them via format-level .fields in GfxXXXFormats.def or per-instruction .operand_fields overrides in GfxXXXInstructions.def. Tablegen generates GfxXXX_operands.inc; each GfxXXX.hpp already includes it and applies requirements to the MCID table. You do not edit the .hpp.

Example — format-level fields in Gfx1250Formats.def (applies to all instructions using this format):

DEF_FORMAT(TENSOR,
    .unit = TensorUnit,
    .encoding = {64},
    .maxOperands = 4,
    .fields = {
        {S0, vaddr0, sgpr_srg0, sgpr, 128},
        {S1, vaddr1, sgpr_srg1, sgpr, 256},
        {S2, vaddr2, sgpr_srg2, sreg, 128},
        {S3, vaddr3, sgpr_addr, sreg, 128},
    },
)

Example — per-instruction override with DEF_T:

DEF_T(BufferLoadB128Inst, "buffer_load_b128",
    .format = MUBUF_LOAD,
    .operand_fields = { {D0, .size=128} }
)

Example — per-instruction override inside DEF_BATCH:

DEF_BATCH(.format = MUBUF_LOAD,
    BufferLoadB32Inst, "buffer_load_b32", .cost = {12, 108}, .logical = "BufferLoadB32",
    BufferLoadB128Inst, "buffer_load_b128", .cost = {12, 116}, .logical = "BufferLoadB128",
        .operand_fields = { {D0, .size=128} },
)
Dual-encoding operand overrides (.alt_operand_fields)

Instructions with dual encodings (e.g. VOP2 primary + VOP3 promoted) may need different operand layouts for each encoding. Use .alt_operand_fields for the promoted encoding:

DEF_T(VCndmaskB32Inst, "v_cndmask_b32", .format = VOP2, .logical = "VCndMaskB32",
    .operand_fields = {
        {D0, vdst, vgpr, 32},
        {S0, src0, src, 32},
        {S1, vsrc1, vgpr, 32},
        {S2, src2, vcc, 64},
    },
    .alt_operand_fields = {
        {D0, vdst, vgpr, 32},
        {S0, src0, src, 32},
        {S1, src1, src, 32},
        {S2, src2, sreg, 64},
    },
)

When to add: Instruction requires non-default register sizes or types for the IR verifier. Single-register instructions (32-bit) usually need no override.

Step 7: Run Tablegen

Regenerate IR classes and bindings:

cd build
cmake ..
make tablegen_generated

This auto-generates:

  • LogicalInstructions_generated.hpp - C++ IR classes
  • LogicalOpcodes_generated.inc - Opcode enums
  • StinkyBuilder_*.inc - C++ builder methods
  • IRMnemonics_generated.inc - Mnemonic -> ASM mappings
  • RocisaGfx1250Mappings.inc - Rocisa -> ASM opcode mappings

The mnemonic mapping automatically enables Logical IR -> Assembly IR conversion.


Quick Reference: Instruction Metadata Locations

Instruction definitions and costs live in .def files; tablegen generates .inc files that the .cpp includes. Rocisa mappings stay in the .cpp.

Per-architecture layout

Metadata Type Location What to Modify
Definitions + costs (DEF_T / DEF_BATCH) hardware/src/gfx/GfxXXX/GfxXXXInstructions.def Add DEF_T(...) or entry in DEF_BATCH(...) with .format, .flags, .cost, .logical
Formats (optional) hardware/src/gfx/GfxXXX/GfxXXXFormats.def Add DEF_FORMAT if needed
Generated (do not edit) hardware/generated/GfxXXX_init.inc, GfxXXX_costs.inc, GfxXXX_operands.inc Produced by tablegen from .def
Rocisa LogicalToArch map Auto-generated from .logical in .def (or manually in GfxXXX.cpp) Prefer .logical = "Name" in the DEF_T / DEF_BATCH entry
Operand requirements GfxXXXFormats.def / GfxXXXInstructions.def Define .fields in format or .operand_fields / .alt_operand_fields per-instruction; tablegen -> *_operands.inc (included by GfxXXX.hpp)
Modifier-dependent costs GfxXXXInstructions.def .costOverride = { { MatrixFmtModifiers(...), cycle, latency } } alongside .cost

Gfx1250

Edit hardware/src/gfx/Gfx1250/Gfx1250Instructions.def (and optionally Gfx1250Formats.def in same folder), then rebuild. Use DEF_T for standalone instructions or DEF_BATCH for groups sharing a format. Add .logical in the .def to auto-generate the Rocisa LogicalToArch map. The corresponding Gfx1250.cpp only holds manually-added Rocisa maps if needed. Instruction definitions and costs come from the .def files and generated .inc files.

See also