Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Peephole Optimization

All addresses in this page apply to ptxas v13.0.88 (CUDA 13.0). Other versions will differ.

The peephole optimization pass in ptxas is the single largest subsystem by code volume in the entire binary. Three monolithic dispatch functions -- totaling approximately 750 KB of machine code -- implement a brute-force pattern-match-and-rewrite engine that recognizes instruction idioms in the internal IR and replaces them with more efficient SASS instruction forms. Each dispatch function serves a different compilation context (generic, SM120-specific, and post-scheduling), but all three share the same architecture: a giant opcode-based switch dispatches to hundreds of pattern matchers; the highest-priority match wins; the winning rewrite modifies the instruction in-place.

None of the five mega-dispatchers can be decompiled by Hex-Rays due to their extreme size (204--280 KB each). All analysis in this page derives from disassembly, call graphs, and the 3,185 pattern-matcher functions that they invoke.

Scale Summary

Dispatch functionBinary sizeInstructionsPattern matchersPrimary opcodes hitSecondary switchesTotal switch casesLargest template tableEntry trampolineContext
sub_169B190280 KB65,999762249 / 3731102,347245 (cases 0..244)sub_B12930Generic (all SM)
sub_143C440233 KB~56,2411,087203 / 373851,971190 (cases 0..189)sub_B12940SM120-specific
sub_18A2CA0231 KB54,0431,330203 / 37386~1,970--sub_B12950Third SM target (likely SM103 / SM110 / SM121)
sub_198BCD0233 KB54,0431,336203 / 373851,966190 (cases 0..189)sub_B12960Post-scheduling
sub_BA9D00204 KB48,0531,327203 / 373------sub_B12970Fourth SM target (likely SM103 / SM110 / SM121)

All three primary switches dispatch over the same 0..372 opcode space, but the generic dispatcher recognizes 249 distinct opcodes, while the SM120 and post-schedule dispatchers each handle only 203 -- the remaining opcodes fall through to the shared default in each pass. The generic pass also reaches pattern/template ID 244 (its largest secondary table is the 245-case rewrite selector at 0x169DC25), whereas SM120 and post-schedule top out at template ID 189 (190-case tables at 0x144503C and 0x199488C respectively). This is the deepest structural asymmetry between the three passes: the generic peephole both observes more opcode classes and rewrites them into a wider template namespace. All three share an identical 72-case rewrite-action table (at 0x143FB8B, 0x16A166C, 0x198F41B) plus 50--52-case secondary tables that gate medium-frequency rewrites.

All five entry trampolines (sub_B12930, sub_B12940, sub_B12950, sub_B12960, sub_B12970) are 11-byte thunks that strip or forward one argument and tail-call the corresponding giant. The two newly-identified dispatchers sub_18A2CA0 and sub_BA9D00 share the call-graph fingerprint of the SM120/post-schedule giants: identical helper set (sub_B28F10/sub_B28F20 write scheduling-class bytes at descriptor offsets +0x0E / +0x0F; sub_BA9C30/sub_BA9C50/sub_BA9C70/ sub_BA9CB0/sub_BA9CF0 write the resource-class word at +0x8C), identical 373-case primary opcode switch, identical sole-caller-via-thunk linkage from the contiguous sub_B129xx vtable column. They are almost certainly the per-target dispatchers for the remaining SM families documented in the wiki (SM103 Blackwell Ultra GB300, SM110 Jetson Thor, SM121 DGX Spark); mapping each function to a specific SM ID requires inspecting the call-site that loads the vtable column at 0xB12930-0xB12970 and is left for follow-up.

Pipeline Position

 IR instruction stream
       |
       v
 sub_B12930 -----> sub_169B190   (generic peephole)
       |
       v
 sub_B12940 -----> sub_143C440   (SM120 peephole, RTX 50-series / Pro)
       |
       v
 [instruction scheduling]
       |
       v
 sub_B12960 -----> sub_198BCD0   (post-schedule peephole)
       |
       v
 [instruction encoding via vtable]

The generic and SM120 dispatchers run before scheduling; the post-scheduling dispatcher runs after. The SM120 dispatcher (sub_143C440) appears to be architecture-gated -- it is called only when compiling for SM 120 targets (consumer RTX 50-series, enterprise Pro GPUs).

Dispatch Architecture

All three mega-dispatchers follow the same algorithm.

Entry and primary switch

push callee-saves
sub  rsp, 10h
mov  rbp, rdi            ; ctx
mov  rbx, rsi            ; instruction node
mov  [rsp+var_2C], -1    ; best_template_id = NONE
mov  [rsp+var_30], -1    ; best_priority    = NONE
movzx edi, word [rsi+0Ch]; read opcode field
call sub_13B9DC0          ; identity / normalization (returns opcode)
cmp  ax, 174h             ; 373 cases (opcodes 0..372)
ja   default
jmp  [jump_table + rax*8] ; PRIMARY SWITCH on opcode

The 16-bit opcode at instruction node offset +0x0C selects a primary case. All three dispatchers use 373-case primary switches.

Per-case pattern matching

Within each primary case, the dispatcher:

  1. Calls a sequence of pattern-matcher functions, passing pointers to best_template_id and best_priority as out-parameters.
  2. Each matcher may update these if it finds a match with higher priority than the current best.
  3. After all matchers for the opcode have run, the dispatcher checks best_template_id. If it is no longer -1, a secondary switch on the template ID selects the rewrite action.

The secondary switches are embedded inside the giant function. sub_143C440 contains 85 secondary jump tables (sizes 6--190 cases) totaling 1,598 secondary cases (1,971 including the primary 373-case opcode switch); sub_169B190 contains 110 secondaries totaling 1,974 secondary cases (2,347 with the primary), and sub_198BCD0 mirrors SM120 with 85 secondaries and 1,593 secondary cases (1,966 with the primary). In every dispatcher the largest two secondary tables are the template-rewrite selector (190 or 245 cases) and the 72-case action subtable; the remaining tables are 6--52-case per-opcode priority gates.

Rewrite action

When a rewrite is selected, the action block performs four operations:

setRewrittenOpcode(instr, new_opcode);     // sub_B28F10: writes byte at instr+14
setRewrittenModifier(instr, new_modifier); // sub_B28F20: writes byte at instr+15
setOperandMapping(instr, slot, value);     // sub_BA9CF0: writes instr+72+4*slot
markRewritten(instr);                      // sub_BA9C30 or sub_BA9CB0

sub_BA9C30 (markRewrittenSimple) sets bit 0 of the flags word at instr+140:

*(uint32_t*)(instr + 140) |= 1;

sub_BA9CB0 (markRewrittenComplex) applies priority-aware flag logic that respects existing rewrites from earlier passes -- it sets bits to 0x8 ("superseded") when a higher-priority rewrite exists.

The symmetry of call frequencies in sub_143C440 confirms this: setRewrittenOpcode and setRewrittenModifier are each called exactly 1,759 times -- every rewrite always sets both the opcode and modifier bytes.

Rewrite action value space

The 1,759 rewrite actions in sub_143C440 use 392 distinct (new_opcode, new_modifier) pairs. new_opcode is a small ordinal (0--193, with a gap at 100--159); new_modifier selects the encoding variant. Top pairs by frequency:

new_opcodenew_modifiercountlikely SASS semantics
0x000x0564identity / NOP-fold
0x010x03582-src ALU, modifier 3
0x010x05472-src ALU, modifier 5
0x020x05423-src FMA-class, modifier 5
0x030x0338shift/logic, modifier 3
0x010x0A362-src ALU, modifier 10 (wider)
0x010x13322-src ALU, modifier 19 (packed/FP16)
0x040x0531convert/move, modifier 5
0x000x0229identity / NOP-fold, modifier 2
0x020x13223-src FMA-class, modifier 19 (FP16)

The modifier namespace (22 distinct values observed) is dominated by six encodings: 0x05 (24% of rewrites), 0x03 (19%), 0x13 (15%), 0x0A (11%), 0x0D (9%), and 0x02 (6%).

Concrete rewrite examples from the binary

A typical rewrite block for template_id == 3 at 0x143C5DA:

// Matched pattern: 4-operand instruction foldable to 2-src ALU
setRewrittenOpcode(instr,  1);   // new_opcode = 0x01
setRewrittenModifier(instr, 2);  // new_modifier = 0x02
setOperandMapping(instr, 0, 1);  // dest slot 0 <- source operand 1
setOperandMapping(instr, 1, 2);  // dest slot 1 <- source operand 2
markRewrittenComplex(instr);     // priority-aware flag (existing rewrite may exist)

A simpler rewrite for template_id == 1 at 0x143C63D:

// Matched pattern: single-operand trivial fold
setRewrittenOpcode(instr,  0);   // new_opcode = 0x00
setRewrittenModifier(instr, 2);  // new_modifier = 0x02
markRewrittenComplex(instr);     // no operand remapping needed

The operand mapping count per rewrite varies:

operand mappingscountshare
01,19368%
118110%
226915%
3945%
4--5221%

68% of rewrites use zero operand mappings -- the instruction's existing operands remain in place and only the opcode/modifier bytes change (e.g., folding a redundant modifier or selecting a cheaper encoding). The remaining 32% physically remap operand slots, typically collapsing a multi-source pattern into fewer sources or swapping operand order.

Of the 1,759 rewrites, 1,251 (71%) use markRewrittenSimple (unconditional flag set), and 363 (21%) use markRewrittenComplex (priority-aware); 145 (8%) fall through to a shared exit without an explicit mark call.

Rewrite-Action Enumeration (Generic Dispatcher)

The generic dispatcher's two largest secondary tables expose the entire rewrite namespace. Every case is a contiguous, fixed-shape code block emitting the canonical setRewrittenOpcode / setRewrittenModifier / [setOperandMapping*] / markRewritten / jmp default sequence; the only thing that changes between cases is the immediate operands. By decoding the mov $imm32, %esi bytes that feed sub_B28F10 and sub_B28F20 directly out of the binary, the full (template_ID -> new_opcode, new_modifier, #operand_mappings) mapping can be recovered without running the compiler.

72-case action subtable at 0x16A166C

The first 60 cases form a tightly packed band at 0x16B8FF6--0x16B98CC (consecutive +39-byte blocks); the remaining 11 cases (60--71) sit slightly earlier at 0x16B8A53--0x16B6283. Despite spanning 72 entries the table uses only two distinct new_modifier values: 0x03 and 0x19 (decimal 3 and 25), partitioning the table into two halves of 35 and 36 cases (and 69 of 71 cases skip operand remapping entirely). This is the signature of a paired-form selector: each logical rewrite has two SASS encoding variants (modifier 0x03 = primary form, modifier 0x19 = alternate / predicated / extended-immediate form), and the case ID picks which variant to emit. The remaining two cases (IDs 69 and 71) are 200-byte blocks with 12 setOperandMapping calls -- they emit full multi-operand reshapes for new_opcode = 59 and new_opcode = 62, the only entries in this table that remap operands.

The full inventory of representative rewrite actions (case ID -- new_opcode -- new_modifier -- character):

CasenewOpnewMod#mapInterpretation
14631Single-mapping ALU swap (the only +60-byte block among cases 1--60; the lone operand remap probably swaps source order to satisfy commutativity canonicalization)
249250Pure encoding-form swap to predicated/extended variant of op 49
121030Identity rewrite of opcode 10 (SHF) to primary form
1313250Paired alternate form of op 13
19130Trivial opcode-1 (2-src ALU) move-elimination -- the most common immediate-fold target
20230Opcode-2 (3-src FMA-class) collapse to plain 2-source primary form
33030Identity / NOP-fold rewrite (newOp 0 is the canonical drop-instruction marker)
34330Opcode-3 (shift/logic) primary-form rewrite
42--5418--34250Block of 13 contiguous predicate/uniform-register canonicalizations (SEL, MOV, predicate operands collapsed to mod-25 alternate form)
60--6460--6430Pure modifier flips for ALU opcodes 60--64 (signed/unsigned or 32/64-bit pair selectors)
65--6864--6925 / 30Memory-ordering canonicalization: (.gpu, .acquire) paired with (.sys, .release) -- mod=3 vs mod=25 selects the SASS bit that encodes the scope/order combination
69592512Full operand reshape for tensor/HMMA-class opcode 59 (12 sequential setOperandMapping calls cover dst + 4 sources + accumulator + meta)
71622512Full operand reshape for paired tensor/HMMA-class opcode 62 (same shape as case 69; the two are the only "wide" rewrites in this table)

In total the 72-action table commits 71 distinct rewrites: 36 select new_modifier = 0x19 (predicated / alternate / extended-imm encoding) and 35 select new_modifier = 0x03 (primary register-register encoding). The table is best understood as: "given the matched ISA group, redirect to one of 71 specific SASS opcode + encoding-variant cells".

245-case template subtable at 0x169DC25

The 244 non-default cases are distributed across four contiguous 4 KB pages of code: 0x16DC... (68 cases), 0x16DD... (87), 0x16DE... (51), 0x16DF... (38). Spacing between consecutive blocks: 160 of 243 pairs are exactly +39 bytes (66%, the no-operand-mapping block); 18 pairs are +75 (one mapping), 17 pairs +57 (no mapping but a longer flag-set sequence), 14 pairs +84 (two mappings), 11 pairs +102 (three mappings), with the tail extending to 337 bytes for the largest single rewrite.

Modifier distribution across the 244 cases:

new_modifierCountShareLikely SASS encoding role
0x057832%Default register-register integer/FP form
0x224619%Wide-immediate / 64-bit-immediate form
0x033715%Primary 2-source ALU encoding
0x0B219%Predicated / conditional form
0x19208%Alternate register file (uniform-reg path)
0x0A177%3-operand FMA encoding
0x0694%Constant-buffer source variant
0x0794%Shared-memory / addressing-mode variant
0x1362%FP16x2 / packed-half encoding
0x171<1%Singleton high-modifier (likely tensor descriptor form)

Operand-mapping distribution -- how many slots get physically remapped per rewrite:

#mappingsCasesShareRewrite character
015764%Pure opcode/modifier swap; instruction's operands stay in place
1219%Source-order swap or single-operand canonicalization
2229%Two-source ALU combining (e.g., FMA fold of mul + add)
3198%Three-source FMA / MAD synthesis
4115%Four-operand pattern (predicated FMA, conditional select)
562%Five-operand patterns (extended FMA with carry)
652%Six-operand patterns (predicate + FMA + condition)
71<1%Single case (template 199): seven-operand rewrite
81<1%Single case (template 1): eight-operand wide load/store
221<1%Template 241 at 0x16DC5FC -- 22 operand mappings in a single 337-byte block; this is the full tensor-instruction permutation (8 dst slots + 8 src slots + 6 meta/descriptor slots), the most operand-heavy rewrite in the generic peephole pass

The 244 unique new_opcode values are essentially a 1:1 mapping from template ID to a target SASS opcode -- every template ID rewrites to a different opcode. The opcode space spans 0--234 with no observable clustering by template ID (template 241 emits opcode 234; template 1 emits opcode 68; template 192 emits opcode 203), confirming that template IDs are assigned by pattern-matcher priority order, not by target opcode.

Representative rewrite categories

Cross-referencing the decoded (newOp, newMod, #mappings) triples with the matcher priorities, slot-ID guards, and call frequencies (Sections above), the 245 + 72 cases group into roughly nine recognizable rewrite categories. The "#" column lists the approximate count of rewrites in that category across both tables combined; the "Examples" column gives concrete (table, caseID, newOp, newMod) references.

Category#MechanismExamples
Move elimination / NOP fold (drop redundant mov)~22Rewrite to new_opcode = 0, drop all operands, markRewrittenSimple72-action case 33 (0, 3); generic-pair (0, 5) 64x; (0, 2) 29x
Modifier-flip canonicalization (no operand change)~157setRewrittenOpcode/Modifier only, zero mappings72-action cases 2-59; 245-template 64% majority
Signed/unsigned pair selection~12Paired (mod=3, mod=25) cells across consecutive case IDs72-action 12/13, 14/15, 16/17
Predicate canonicalization (collapse setp chains, flip polarity)~20new_modifier = 0x0B (predicated form), often 1-2 operand remaps245-template cases with mod=11
Immediate fold (constant on source)~18new_modifier = 0x06 (constant-buffer source variant) or 0x22 (wide-imm); operand-mapping inserts the immediate in slot 1 or 2245-template mod=6 cases; mod=34 cases with 1-2 mappings
Two-source ALU combine (add + mul -> fma-style 2-src)~222 operand mappings consolidating sources from two parent instrs245-template cases with #map=2, mod in {3,5}
Three-source FMA synthesis~193 operand mappings; emits FMA-class opcode 2/87/108245-template cases 87 (op=108, mod=10, #map=5); cases with #map=3
Memory-ordering / scope rewrites~8(.gpu, .acquire) vs (.sys, .release) slot-0xD2/0xD3 pair gated; emits paired mod=3/mod=25 cells72-action cases 60-68
Tensor / HMMA full reshape46+ operand mappings, new_modifier in {0x05, 0x13, 0x17}, full descriptor slots72-action 69 & 71 (12 mappings); 245-template 241 (22 mappings); 245-template 1 (8 mappings)
Predicated select / conditional move collapse~114 operand mappings; preserves predicate slot, swaps true/false sources245-template cases with #map=4

The "modifier-flip canonicalization" bucket dominates both tables -- two thirds of all rewrites in the generic pass only change the SASS encoding byte without touching operands. This is consistent with peephole's primary role as the final stage that selects the cheapest encoding form for each already-correct instruction before emission, rather than performing substantive algebraic rewrites (those happen in earlier IR passes).

QUIRK -- modifier 0x22 is wider than the public PTX modifier space

The 245-template subtable contains 46 rewrites with new_modifier = 0x22 (decimal 34), a value that does not correspond to any documented PTX modifier enum. Cross-referencing with the encoder vtables, modifier 0x22 appears to select a SASS encoding variant that uses a 64-bit constant slot in the descriptor table -- the rewrite preserves operand kind but redirects the encoder to a wider immediate form. Modifier values 0x22, 0x19, and 0x0B together account for 36% of all 245-template rewrites; none of these three values appears in either the SM120 or post-schedule 190-case tables, which suggests they are generic-pass-only encodings the later passes never emit.

QUIRK -- template 241 is the most expensive single rewrite in ptxas

Template ID 241 at 0x16DC5FC rewrites to new_opcode = 234, new_modifier = 3 and performs 22 sequential setOperandMapping calls in a 337-byte block -- by far the largest single rewrite block in either dispatcher. Twenty-two operand mappings is more than the explicit operand slots of any ordinary SASS instruction, so the rewrite is necessarily reshaping a multi-tile tensor descriptor: 8 source-tile slots, 8 destination-tile slots, and 6 metadata slots (layout, swizzle, accumulator type). This is the heaviest single transformation peephole performs, and -- combined with the fact that template ID 244 is the highest ID reached by the generic pass -- suggests that the topmost 4-5 template IDs (240-244) form a "tensor-shape canonicalization" cluster that runs only in the generic pre-schedule context.

QUIRK -- the 72-action table's modifier space is binary

Across all 71 active cases of the 72-action table, the new_modifier byte takes only two values: 0x03 (35 cases) and 0x19 (36 cases). The pair is too tight to be coincidence: cases 12 (op=10,mod=3) and 13 (op=13,mod=25) look like a (signed, unsigned) pair; cases 14/15, 16/17, 18/19 follow the same alternating rhythm. The 72-action table is therefore not a generic rewrite catalog -- it is a paired-form selector: matchers that reach this table have already decided which logical operation to emit and only need to pick between two SASS encoding variants. This binary nature explains why the table is identical across all three dispatcher contexts (0x143FB8B for SM120, 0x16A166C for generic, 0x198F41B for post- schedule) -- the (primary, alternate) encoding choice is invariant under architecture and scheduling state.

Pattern Matcher Signature

Every one of the 3,185 pattern matchers shares the same prototype:

char __fastcall match(
    int64_t ctx,           // a1: peephole optimization context
    int64_t instr,         // a2: instruction node being examined
    int32_t *template_id,  // a3: output -- combined opcode / template ID
    int32_t *priority      // a4: input/output -- current best priority
);

The function returns a char (the last comparison result, used for early-exit optimization in the caller), but the meaningful outputs are *template_id and *priority.

Matching algorithm

Every matcher performs a deeply-nested chain of checks:

Step 1 -- Modifier/property checks. Call queryModifier(ctx, instr, slot) (sub_10AE5C0) repeatedly. Each call returns an enumerated value for a specific instruction property:

if (queryModifier(ctx, instr, 0xDC) != 1206) return 0;  // field 220: encoding type != .f32
if (queryModifier(ctx, instr, 0x163) != 1943) return 0;  // field 355: rounding != .rn
if (queryModifier(ctx, instr, 0x7E) - 547 > 1) return 0; // field 126: data type not in {.f32, .f64}

The slot indices are field IDs from the same flat property namespace used by the ISel matchers (see isel.md field-ID table). queryModifier is literally DAGNode_ReadField (sub_10AE5C0); peephole and ISel share the same property-bag abstraction. The 42 slot IDs observed in the peephole matchers, with their decoded semantics, guard values, and value-to-PTX modifier mappings:

SlotDecSemantic nameObserved guard valuesDecoded meaning
0x055Ori opcode ID12Internal opcode number (e.g., 12 = tensor-class)
0x7B123operation class536Major instruction family tag
0x7E126data type qualifier547, 548547 = .f32, 548 = .f64; range check - 547 <= 1
0x88136sub-operation modifier406--408, 598, 599Instruction sub-variant (e.g., ADD vs FADD vs FMUL)
0x90144FP precision class628, 629Range - 628 <= 1; selects FP16 vs FP32 operand path
0xA1161addressing mode variant700Memory addressing mode (register-indirect vs offset)
0xBE190operation subtype815Zero-operand instruction subtype (NOP/barrier class)
0xD2210memory scope1177, 11811177 = .gpu (device), 1181 = .sys (system) scope
0xD3211memory ordering1181Acquire/release/relaxed semantics
0xDC220encoding property (type)12061206 = .f32 encoding tag for SASS bitfield selection
0xF2242width / size qualifier1281, 12821281 = 32-bit, 1282 = 64-bit operand width
0x101257async copy routing1332Async memory operation routing value
0x119281address space qualifier1435--1440.global / .shared / .local / .const (6 spaces)
0x126294constraint field1493Encoding constraint check
0x127295extended constraint1499Secondary encoding constraint
0x142322instruction form1800Instruction encoding form class
0x152338operand layout tag1871, 1873, 1874Source/dest operand slot arrangement variant
0x155341source modifier1881, 1882Range - 1881 <= 1; source operand modifier (abs/neg)
0x159345rounding mode selector1899--19031900 = .rn, 1901 = .rz, 1902 = .rm, 1903 = .rp
0x15C348precision qualifier1912, 1915FP precision tag (.tf32, .bf16, etc.)
0x163355extended property (rnd)19431943 = .rn extended rounding mode confirmation
0x167359operand negation mask1957, 1961Source-operand sign-flip for FP instructions
0x178376extended property A2035Extended instruction property (tensor/MMA class)
0x179377extended property B2037--20415-value range; tensor layout variant
0x18A394dual-issue / sched hint--Scheduling hint for dual-issue eligibility
0x18D397encoding validity stamp2115Post-ISel seal: 0x843 = bits {0,1,6} set in SASS dword 0
0x196406MMA type A2146Matrix multiply source type A (FP16/BF16/TF32/INT8)
0x197407MMA type B--Matrix multiply source type B
0x199409MMA accumulator type--Accumulator precision for tensor ops
0x19D413extended qualifier2167, 21682-value discriminator for tensor instruction shape
0x1A8424uniform register hint2214--2225Bitmask 739; uniform register allocation eligibility
0x1AD429extended qualifier B2253--22575-value range; tensor instruction extended modifier
0x1AE430warp shuffle mode A--SHFL sub-operation variant
0x1AF431warp shuffle mode B--SHFL companion modifier
0x1B2434FP composition2274FP instruction composition (fused vs separate)
0x1D1465codegen control A--Code generation control property
0x1D2466codegen control B--Code generation control property
0x1E0480encoding format class2478--2481Selects SASS encoding format (3-src vs imm vs reg-reg)
0x1E4484extended modifier C--Blackwell-era extended property
0x1EC492extended modifier D--Blackwell-era extended property
0x216534MMA layout descriptor2717HMMA/DMMA matrix layout and tiling descriptor
0x253595extended field (max)2937, 2938Highest field ID observed; tensor instruction tag

Values are drawn from a global enumeration (~2,850 entries, max 2829) that is disjoint from the field-ID namespace. The encoding bitfield LUT at VA 0x23F2E00 maps value IDs to bit positions and masks in the SASS instruction word. The most frequently read slots in the peephole matchers are 0x159 (rounding, ~400 reads), 0x7E (data type, ~300), 0x88 (sub-op, ~200), and 0x18D (validity stamp, ~150).

Step 2 -- Operand count. Check the number of explicit/fixed operands and the total operand slot count:

int fixed = getExplicitOperandCount(instr);  // sub_B28F50: returns *(instr+92)
int total = getTotalOperandSlots(instr);     // sub_B28F40: returns *(instr+40)+1 - *(instr+92)

Step 3 -- Operand type and register class validation. For each operand slot, retrieve the operand pointer and check its kind:

void *op = getOperand(instr, idx);   // sub_B28F30: returns *(instr+32) + 32*idx
byte kind = *(byte*)op;
if (!isRegister(kind))   return 0;   // sub_13B9CD0: kind == 2
if (!isImmediate(kind))  return 0;   // sub_13B9CE0: kind == 1 (alt check)

Register class is checked against expected values:

int regclass = getRegisterClass(*(uint32_t*)(op + 4)); // sub_13B9CC0
if (regclass != 1023 && regclass != 1) return 0;       // 1023 = wildcard

Step 4 -- Priority gate. If all checks pass and the current priority allows it:

if (*priority <= threshold) {
    *priority = threshold + 1;
    *template_id = combined_opcode_id;
}

Since matchers are called sequentially and each checks the running maximum, the highest-priority match always wins.

Operand Type Discriminators

Three families of trivial single-instruction functions serve as operand type predicates, one family per dispatch context:

SM120 matchers (Zone A of sub_143C440)

FunctionTestSemantic
sub_13B9CD0kind == 2isRegister
sub_13B9CE0kind == 1isImmediate
sub_13B9D00kind == 2 || kind == 1isRegOrImm
sub_13B9D10kind == ?isConstantBuffer
sub_13B9D40kind == ?isPredicate
sub_13B9D50kind == ?isUniformRegister
sub_13B9CC0extracts classgetRegisterClass (1023 = wildcard)

Generic matchers (Zone A of sub_169B190)

FunctionTestSemantic
sub_15F59C0a1 == 2isRegister
sub_15F59D0a1 == 1isImmediate
sub_15F59E0a1 == 0isNone
sub_15F59F0a1 == 10isConstantMemory
sub_15F5A00a1 == 9isTexRef
sub_15F5A30a1 == 3isPredicate / isConstImm
sub_15F5A40a1 == 15isUniformRegister / isTrueConst
sub_15F5A80a1 == 6isLabel
sub_15F5A90a1 == 11isTexture
sub_15F5AB0identitygetOperandValue

Post-schedule matchers (Zone A of sub_198BCD0)

FunctionTestSemanticCall count
sub_1820170identitygetOpcodeRaw9,278
sub_1820180a1 == 2isRegOperand2,743
sub_1820190a1 == 1isImmOperand677
sub_18201A0a1 == 8isUniform7
sub_18201B0a1 == 10isPredicateReg1,228
sub_18201C0a1 == 9isTexRef211
sub_18201D0a1 == 5isConstBuf14
sub_18201E0a1 == 4isAddress9
sub_18201F0a1 == 3isConstImm1,044
sub_1820200a1 == 15isTrueConst1,044
sub_1820210a1 == 7isBarrier9
sub_1820220a1 == 12isSurface12
sub_1820230a1 == 11isTexture12
sub_1820240a1 == 6isLabel2
sub_1820250a1 == 14isSpecialReg2
sub_1820260a1 == 13isUnknown6

Priority System

Matchers use a strict numeric priority to resolve conflicts when multiple patterns match the same instruction. Higher priority means more specific and/or more profitable transformation.

Priority rangeDescriptionExample
1--2Trivial matches (simple mov, basic arithmetic)Single-operand passthrough
5--11Common 2--3 operand combining patternsStandard FMA combines
14--20Complex 4-operand patterns with constraintsMulti-source ALU combines
22--31Highly specific multi-operand patternsWide register + predicated ops
33--36Maximum specificity (8--9 operands + all modifiers)Full tensor instruction forms

Pattern IDs occupy disjoint ranges per dispatcher: the generic pass reaches template ID 244 (245-case rewrite table at 0x169DC25), while SM120 and post-schedule cap at template ID 189 (190-case tables at 0x144503C and 0x199488C). Multiple matchers can target the same pattern ID with different priorities, creating a priority cascade.

Instruction Node Layout

The peephole subsystem reveals the following fields of the instruction IR node:

OffsetSizeFieldAccessor
+0x001 BOperand type tagisRegister, isImmediate, etc.
+0x044 BPrimary value (register number / immediate)getRegisterClass / getOperandValue
+0x0C2 BOpcode number (16-bit)Direct read in dispatch entry
+0x0E1 BRewritten opcodesub_B28F10 (setRewrittenOpcode)
+0x0F1 BRewritten modifiersub_B28F20 (setRewrittenModifier)
+0x144 BSecondary register fieldDirect read
+0x208 BOperand array base pointersub_B28F30 base address
+0x284 BTotal operand countPart of sub_B28F40 computation
+0x48varOperand mapping table (4 B per slot)sub_BA9CF0 writes here
+0x5C4 BExplicit operand countsub_B28F50 returns this
+0x8C4 BFlags wordBit 0 = rewritten (set by sub_BA9C30)

Each operand is a 32-byte record at base + 32 * index:

Operand offsetSizeContent
+01 BType tag (1=imm, 2=reg, 3=constImm, 10=pred, 15=trueConst, ...)
+44 BPrimary value (register ID; 1023 = wildcard / any-reg)
+204 BSecondary value (modifier / sub-register)

Code Duplication

The pattern matchers exhibit extreme structural duplication. Groups of 2--10 functions are near-identical clones differing only in numeric constants (the specific opcode/modifier values they check, the template ID they assign, and the priority level).

Observed clone clusters in sub_169B190's matchers:

Cluster sizeCountByte size eachAddress range example
~5,560 B5 functions5,5600x167CBB0--0x16E7D20
~5,282 B10 functions5,2820x167E3A0--0x16807E0
~5,298 B4 functions5,2980x16EA5F0--0x16ECA30
~5,846 B3 functions5,8460x16EDC00--0x16EE8B0
~2,718 B7 functions2,7180x166F260--0x1692B60
~2,604 B6 functions2,6040x166AC30--0x166E170

Similarly, in sub_198BCD0's matchers, eight functions of exactly 5,282 bytes each (sub_1982810, sub_1982AE0, sub_1982DB0, sub_1983080, sub_1984B40, sub_1984E10, sub_19850E0, sub_19853B0) share identical structure, varying only in the opcode/modifier constants passed to sub_10AE5C0.

This strongly suggests compiler-generated code from C++ templates or macros that instantiate one matcher function per instruction variant from ISA specification tables -- a pattern consistent with NVIDIA's internal build tooling.

Size Distribution of Matchers

SM120 matchers (1,087 functions, 429 KB)

Size rangeCountDescription
< 200 B37Simple 1--2 modifier checks
200--400 B520Typical 4--8 modifier checks
400--600 B4556--12 modifier checks + operand validation
600--800 B66Complex multi-operand patterns
> 800 B9Deepest nesting, most constrained patterns

Generic matchers (762 functions, ~310 KB)

Size rangeCountDescription
~2,200 Bmost common2--4 instruction field checks
~2,800 BmoderatePatterns with operand constraints
~3,500--4,000 BfewerComplex multi-operand patterns
~5,500--8,500 Brare12+ modifier checks, 8--9 operands

Post-schedule matchers (~1,336 functions)

Size rangeCountDescription
~2,200 Bmost commonSimple 2-instruction patterns
~2,500 Bcommon3-instruction patterns
~3,100 BmoderatePatterns with predicate checks
~5,300 BfewMulti-instruction sequences (8+ operands)
~6,800 B1Largest matcher (sub_1980D10)

Representative Matcher Examples

Simplest: sub_143C3B0 (132 bytes, priority 2, template 1)

Checks: no explicit operands, 2 total slots, first operand is register-or-immediate with register class 1023 or 1. Matches a trivial mov-type instruction for passthrough combining.

Moderate: sub_13CF0C0 (426 bytes, priority 15, template 28)

Checks 5 modifiers: slot 0xD3 == 1181, slot 0xD2 == 1177, slot 0x0C == 59, slot 0xB3 == 772, slot 0xC8 == 1107. Then validates 1 explicit register operand plus 4 additional operands (register, register, immediate, predicate).

Complex: sub_1615980 (priority 36, template 25 -- highest observed priority)

Checks 12 modifier slots: 0x05 == 12, 0xDC == 1206, 0x253 in {2937,2938}, 0x126 == 1493, 0xF2 in {1281,1282}, 0x163 == 1943, 0x178 == 2035, 0x179 in {2037..2041}, 0x1AD in {2253..2257}, 0x7E in {547,548}, 0x19D in {2167,2168}, 0x18D == 2115. No fixed operands, 7 variable operands, each of type 10 (constant memory) with register class 1023 or specific flag constraints. This is the most constrained pattern observed -- likely a fully specified tensor instruction variant.

Post-schedule: sub_1834600 (pattern 17, priority 16)

Checks modifier slots 0xD3 == 1181, 0xD2 == 1177, 0x0C in {60,61}, 0xB3 == 772, 0xC8 == 1107. Then: first operand offset == 1, that operand is immediate, total operand count == 5, followed by register pattern checks.

Infrastructure Helper Functions

Core accessor (sub_10AE5C0, 60 bytes)

The single most-called function in the peephole subsystem (30,768 callers across the full binary). Queries a property of an instruction node by slot ID:

int queryModifier(int64_t ctx, int64_t instr, int slot) {
    if (hasProperty(instr, slot))        // sub_10E32E0
        return getPropertyValue(instr, slot); // sub_10D5E60
    return 0xFFFFFFFF;                   // property not present
}

Node accessors

FunctionSizeSemanticsCall frequency
sub_B28F3012 BgetOperand(instr, idx) -- returns *(instr+32) + 32*idx31,399
sub_B28F4010 BgetTotalOperandSlots(instr) -- returns *(instr+40)+1 - *(instr+92)~2,500
sub_B28F504 BgetExplicitOperandCount(instr) -- returns *(instr+92)~2,100

Rewrite helpers

FunctionSemanticsCall frequency in sub_143C440
sub_B28F10setRewrittenOpcode(instr, byte) -- writes instr[14]1,759
sub_B28F20setRewrittenModifier(instr, byte) -- writes instr[15]1,759
sub_BA9CF0setOperandMapping(instr, slot, val) -- writes instr[72+4*slot]993
sub_BA9C30markRewrittenSimple(instr) -- instr[140] |= 11,222
sub_BA9CB0markRewrittenComplex(instr) -- priority-aware flag update361

The ratio of markRewrittenSimple (1,222) to markRewrittenComplex (361) shows that approximately 77% of rewrites are straightforward replacements, while 23% involve priority negotiation with competing rewrites.

Call Frequency in sub_169B190 (Generic Dispatcher)

CalleeCountRole
sub_B28F10 (setRewrittenOpcode)2,142Write new opcode byte
sub_B28F20 (setRewrittenModifier)2,142Write new modifier byte
sub_15F59B0 (getOperandValue)1,736Extract register number
sub_10AE5C0 (queryModifier)1,303Read instruction property
sub_B28F30 (getOperand)1,281Get operand pointer
sub_BA9C30 (markRewrittenSimple)1,261Simple rewrite commit
sub_BA9CF0 (setOperandMapping)855Map operand slots
sub_BA9CB0 (markRewrittenComplex)589Priority-aware commit

Relationship to Instruction Encoding

Each dispatch function's address range is adjacent to a zone of SASS instruction encoders that consume the rewritten instructions:

  • sub_143C440 (SM120) sits before 123 SM120 encoders at 0x14771E0--0x14A3C80 (180 KB), covering 82 unique SASS opcodes with up to 42 encoding variants per opcode.
  • sub_169B190 (generic) sits before 100 encoding table entries at 0x16DF750--0x16FFFF0 and 36 template expanders at 0x1700000--0x1722D60.
  • sub_198BCD0 (post-schedule) operates on already-scheduled instructions, performing strength reduction and idiom recognition on the final instruction stream.

The encoders are called via vtable dispatch, not directly from the peephole functions. Each encoder packs a 128-bit SASS instruction word using sub_7B9B80(state, bit_offset, bit_width, value) for bit-field insertion.

Function Map

AddressSizeIdentityConfidence
sub_B1293011 BEntry trampoline for generic peephole (-> sub_169B190)CERTAIN
sub_B1294011 BEntry trampoline for SM120 peephole (-> sub_143C440)CERTAIN
sub_B1295011 BEntry trampoline for third SM-target peephole (-> sub_18A2CA0)CERTAIN
sub_B1296011 BEntry trampoline for post-schedule peephole (-> sub_198BCD0)CERTAIN
sub_B1297011 BEntry trampoline for fourth SM-target peephole (-> sub_BA9D00)CERTAIN
sub_169B190280 KBGeneric peephole mega-dispatcherHIGH
sub_143C440233 KBSM120 peephole mega-dispatcherHIGH
sub_198BCD0233 KBPost-schedule peephole mega-dispatcherHIGH
sub_10AE5C060 BqueryModifier(ctx, instr, slot)HIGH
sub_B28F10smallsetRewrittenOpcode(instr, byte)HIGH
sub_B28F20smallsetRewrittenModifier(instr, byte)HIGH
sub_B28F3012 BgetOperand(instr, idx)CERTAIN
sub_B28F4010 BgetTotalOperandSlots(instr)CERTAIN
sub_B28F504 BgetExplicitOperandCount(instr)CERTAIN
sub_BA9C30smallmarkRewrittenSimple(instr)HIGH
sub_BA9CB0smallmarkRewrittenComplex(instr)HIGH
sub_BA9CF0smallsetOperandMapping(instr, slot, value)HIGH
sub_13B9CC0smallgetRegisterClass(field)HIGH
sub_13B9CD0smallisRegister(byte)HIGH
sub_13B9CE0smallisImmediate(byte)HIGH
sub_13B9D00smallisRegisterOrImmediate(byte)HIGH
sub_13B9D10smallisConstantBuffer(byte)HIGH
sub_13B9D40smallisPredicate(byte)HIGH
sub_13B9D50smallisUniformRegister(byte)HIGH
sub_13B9DC0smallopcodeIdentity(uint) -- passthroughCERTAIN
sub_1909030smallopcodePassthrough (post-schedule context)HIGH

Macro Instruction Expansion (sub_8127C0)

Separate from the three pattern-match-and-rewrite mega-dispatchers, ptxas contains a dedicated macro instruction expansion pass at sub_8127C0 (10,720 bytes). This pass resolves register-file constraints for composite instructions -- cases where source or destination operands span register files or where multi-word results need splitting into narrower instruction sequences.

It is called from the master lowering dispatcher sub_8380A0 and runs before instruction scheduling.

Two-phase algorithm

Phase 1 -- Operand scanning and constraint annotation. The pass iterates every instruction in the function's linked list (traversing via instr+8). For each instruction, it reads the opcode at instr+72 and dispatches through a 15-family if-else cascade. For each opcode, it calls sub_812550 (getOperandConstraint) on each source operand to determine register-file affinity:

Return valueMeaning
0Unconstrained
-2Constrained to register file B (e.g., even-aligned pair)
-3Constrained to register file A (e.g., odd-aligned pair)
-1Conflict / unresolvable

The pass annotates register descriptor entries (indexed via ctx+88) at reg+76 (constraint code) and reg+80 (target width code), and builds a linked list of instructions requiring expansion (linked via instr+56). Registers consumed by expansion are marked dead (reg+64 = 5).

Phase 2 -- Instruction rewriting. If v187 == 0 (no expansion needed), phase 2 is skipped. Otherwise a cleanup loop purges the worklist: it reads both operand constraint codes, applies modifier- bit adjustments (bits 25-31 can transform -2 to -3/-1), and removes entries where both resolve to -1. The loop repeats until no further removals (fixed-point). The rewrite loop then dispatches by opcode:

for each instr on expansion_worklist (linked via instr+56):
  opc = instr[72]; dst = &instr[84]; src0 = &instr[92]
  src1 = &instr[100]; src2 = &instr[108]; reg = regArray[dst & 0xFFFFFF]
  switch opc:

  case 36 (I2IP), 201 (QMMA_16816), 202 (QMMA_16832):
    cA = getOperandConstraint(ctx, src0)           // sub_812550
    if cA not in {-2,-3}: skip
    w = (cA==-2) ? dword_21D5EE0[src2 & 0xFFFFFF] : dword_21D5F60[src2 & 0xFFFFFF]
    resolved = resolveOperandType(ctx, src0, w)    // sub_800360
    if opc==201: instr[108] = resolved | 0x60000000
    else:        patch slot at nOps + ~((opc>>11) & 2)
    recomputeProperties(ctx, instr)                // sub_92C0D0
    if reg.file != 5:                              // dest still live
      sched = sm_backend->getSchedClass(instr[76]) // vtable[113]
      if opc==202: emit_barrier(ctx, dword_21D6390[(pen>>9)&0xF], 20, ...)
      emit_4op(ctx, 36, sched, dst, src0, RZ, resolved)  // sub_930040
      deleteInstruction(ctx, instr)                // sub_9253C0

  case 18 (FSETP):                                 // nOps==6, modifier filter
    finalizeOperand(ctx, &instr[116])              // sub_800400

  case 29 (PMTRIG), 95/96 (STS/LDG), 190 (LDGDEPBAR), bit-12 set:
    if !sub_747EE0(instr): skip
    idx = nOps + ~((opc>>11) & 2)                  // last-operand index
    pen = &dst[2*idx]; prev = pen-2
    pen[0] = resolveOperandType(ctx, prev, pen[0] & 0xFFFFFF) | 0x60000000
    pen[1] = 0

  case 130 (HSET2), 137 (SM73_FIRST):
    cA = getOperandConstraint(ctx, src0)
    if cA not in {-2,-3}: finalizeOperand(ctx, src0); skip
    w = lookup(cA, src2)                           // same two tables
    if canExpandStoreChain(ctx, instr):            // sub_8125E0
      instr[108] = resolved | 0x60000000           // in-place patch
    else:
      emit_MOV(ctx, 130, 20, dst, src0)            // sub_92E720
      emit_store(ctx, 201, instr[76], dst, src0)   // sub_92FF10
      deleteInstruction(ctx, instr)

  case 149 (UFLO):
    if getOperandConstraint(ctx, dst) in {-2,-3}:
      reg.file = 5; if both srcs dead: instr[76] = 20
    elif sub_7D6780(regArray[src0].width):          // vectorize path
      emit_I2IP(ctx, 36, instr[76], dst, src0, neg, RZ)
      deleteInstruction(ctx, instr)
    else: finalizeOperand(ctx, src0)

  case 10 (SHF), 151 (UIMAD), 290 (MOV sm_104):   // three-source
    cS0 = getOperandConstraint(ctx, src0)
    cS1 = getOperandConstraint(ctx, src1)
    if both dead: finalizeOperand both; instr[76] = 20
    elif one live, one file==5:
      emit_I2IP(ctx, 36, w, dst, resolved, RZ, const)
      emit_replacement(ctx, opc, w, dst, src0, src1)
      deleteInstruction(ctx, instr)
    else: finalizeOperand(ctx, src0); finalizeOperand(ctx, src1)

  case 283 (UVIADD):                               // simple extraction
    pen = &dst[2*(nOps-2)]; prev = pen-2
    pen[0] = resolveOperandType(ctx, prev, pen[0] & 0xFFFFFF) | 0x60000000
    pen[1] = 0

Register-file mapping uses dword_21D5EE0[26] (constraint -2, width codes 0--25) and dword_21D5F60[16] (constraint -3, width codes 0--15). dword_21D6390[] (indexed by (operand >> 9) & 0xF) selects the barrier variant for QMMA_16832.

Opcodes handled

OpcodeMnemonicExpansion pattern
10SHFThree-source constraint check; emits I2IP (36) + new SHF when sources span register files
18FSETPPredicate operand finalization when operand count == 6 and modifier bits match
29PMTRIGLast-operand extraction and finalization
36I2IPDestination register marking and two-source constraint checking
60LEPCStore/load legalization: validates flags, checks register file == 6, recursive chain validation via sub_812480
62, 78, 79BAR_INDEXED, RTT, BSYNCSame legalization path as LEPC
95, 96STS, LDGLast-operand extraction for stores; two-source vector-width constraint checking for loads
97STGSource registration for expansion tracking
130HSET2Validates single-def destination, recursive source constraint chains; inserts HSET2 rewrites or converts to opcode-201 stores
137SM73_FIRSTSame path as HSET2
149UFLOTwo-source validation; marks destination with width code 20; vectorization combining
151UIMADShared three-source path with SHF
190LDGDEPBARShared last-operand path with PMTRIG
201, 202QMMA_16816, QMMA_16832Full multi-operand legalization; inserts barrier instructions for QMMA
283UVIADDPenultimate operand extraction and type resolution
290MOV (sm_104)Same constraint path as SHF/UIMAD
bit 12 set(arch-specific)Last-operand extraction for architecture-extended instructions

sub_812550 -- getOperandConstraint

The single most-called helper (32 call sites), this 40-byte function reads the constraint code from the register descriptor for a given operand reference:

int getOperandConstraint(int64_t ctx, uint32_t *operand_ref) {
    int modifier_bits = operand_ref[1];
    int constraint = reg_array[*operand_ref & 0xFFFFFF].constraint;  // reg+76
    if ((modifier_bits & 0xFE000000) == 0)
        return constraint;      // no sub-register modifier => raw value
    // Apply modifier-aware transformations:
    //   constraint -2 + certain modifier combos => -3 or -1
    //   constraint -3 + modifier bit 0x3C000000 => -1; + sign bit => -2
    ...
}

sub_812480 -- validateOperandChain

Recursively walks use-def chains through HSET2 (130) and SM73_FIRST (137) instructions to verify that an entire operand chain is compatible with a target register file. Uses sub_A9BD00 to resolve the register file for a width code, then checks reg+76 and reg+80 agreement.

Knob gate

Option 183 (target profile offset 13176) controls the expansion distance threshold. When enabled, a secondary value at profile+13184 sets the maximum distance between a register definition and its use before the constraint is considered violated. Default threshold: 7.

Function map

AddressSizeIdentityConfidence
sub_8127C010,720 BExpandMacroInstructions (main pass)HIGH
sub_81255040 BgetOperandConstraintHIGH
sub_812480~170 BvalidateOperandChainHIGH
sub_8125E0~450 BcanExpandStoreChainMEDIUM
sub_800470smallisLegalizableMEDIUM
sub_800360smallresolveOperandTypeMEDIUM
sub_800400smallfinalizeOperandMEDIUM

Cross-References

Evidence Index

ClaimSource
sub_143C440 structure, 1,087 matchers, 373-case switchp1.20-sweep-0x13CF000-0x14A4000.txt lines 1--486
SM120 encoder zone (123 functions, 180 KB)p1.20 lines 269--329
sub_169B190 structure, 762 matchers, 280 KBp1.22 lines 1--460, p1.23 lines 1--588
Generic operand discriminators (sub_15F59C0 family)p1.22 lines 181--201
Clone clusters in generic matchersp1.23 lines 156--174
Post-schedule discriminators (sub_1820170 family)p1.25 lines 271--289
sub_198BCD0 structure, 1,336 callees, 373-case switchp1.26 lines 355--398
Post-schedule 5,282-byte clone groupp1.26 lines 401--424
Rewrite helper call frequenciesp1.20 lines 216--227, p1.23 lines 228--237
Priority 36 as highest observedp1.22 lines 316--327
Instruction node layoutp1.20 lines 406--420, p1.22 lines 367--409
Secondary switch inventory per dispatcher (85 / 110 / 85; template caps 244 vs 189)ptxas_switches.json filtered by func_addr
Primary 373-case opcode coverage (249 vs 203 distinct targets)ptxas_switches.json, switches at 0x143C478, 0x169B1C8, 0x198BD08
245-template subtable rewrites, (newOp, newMod, #map) triplesDirect disassembly of blocks at 0x16DC2AF--0x16DFF...; cases enumerated in ptxas_switches.json switch at 0x169DC25; immediate decoding via mov $imm32,%esi opcode scan
72-action subtable rewrites, binary (mod=3 / mod=25) splitDirect disassembly of blocks at 0x16B8A53--0x16B98CC; cases enumerated in ptxas_switches.json switch at 0x16A166C
Template 241's 22-mapping reshape at 0x16DC5FCCall-instruction count in the 337-byte block at 0x16DC5FC; verified by e8 opcode density