суббота, 19 сентября 2026 г.

ptx asm in llvm ir

I asked 7 month ago in r/llvm question how I can insert PTX asm right in LLVM IR and got exactly zero answers. So, as usual, I had to figure it out on my own (depressing little song "No Help is Coming" is playing in the background)

How inline PTX looks like in text form:
%7 = call i32 asm sideeffect "madc.hi.cc.u32 $0,$1,$2,$3;", "=r,r,r,r"(i32 %.sroa.018.0.extract.trunc, i32 %.sroa.282.0.extract.trunc, i32 0) #5, !srcloc !9 

So basically it is just call result-type asm with some arguments in parentheses (note that type of result $0 is i32 and it described after keyword call). If you need result of PTX instruction - just assign it to some variable. Official documentation says that #5 is attributes list - somewhere below it defined as
attributes #5 = { nounwind }
and !9 is metadata - is this case for debug info srcloc:
!9 = !{i32 46731}

Well, that was easy part of story - and now Something Completely Different (tm). LLVM IR is strictly typed (I would say - redundantly strictly), so types first time defined for each argument - like i32 for $1, $2 and $3. Second time - in string called operand constraint codes - in my case it is "=r,r,r,r". And official documentation blatantly lies about them. Let's check some source code - method getRegForInlineAsmConstraint in NVPTXISelLowering.cpp. As you can see it accepts following codes:

  • b - 1bit, predicates
  • c & h - 16bit, like (.b16 / .u16 / .s16)
  • r & f - 32bit, like (.b32 / .u32 / .s32) and .f32 for f
  • l, N, d - 64bit, (.b64 / .u64 / .s64) & .f64 for d
  • q - 128bit since sm70+
  • 0 - meaning is still unknown

Symbol '=' is so called Constraint Modifier:

  • = Write-only output operand (overwrites previous contents)
  • + Read-write operand (input and output tied to the same register)
  • & Early-clobber operand (modified before inputs are consumed)
  • ~ Clobber list marker (tells LLVM a register or memory/flags are modified implicitly
Yet another unpleasant discovery - you can freely swap order of operands - for example this variant is exactly the same as above one:
call i32 asm sideeffect "madc.hi.cc.u32 $0,$2,$1,$3;", "=r,r,r,r"(i32 %.sroa.282.0.extract.trunc, i32 %.sroa.018.0.extract.trunc, i32 0)

This makes the task of parsing & comparison of PTX instructions non-trivial - especially in complex cases like
%1 = call { i32, i32, i32, i32 } asm sideeffect "tex.grad.1d.v4.u32.f32 {$0, $1, $2, $3}, [$4, {$5}], {$6}, {$7};", "=r,=r,=r,=r,l,f,f,f"(i64 %tmp5, float %tmp6, float %tmp7, float %tmp8)

PTX from cicc

Once you understand how inline PTX is represented in LLVM IR, the next step is examining how nvidia’s own internal toolchain leverages it.

While doing some RE of nvidia's llvm-based back-end I dumped inline PTX instructions. Now when I have PTX parser the next logic step is try to parse PTX from cicc and for example try find some undocumented instruction/attributes (which nvidia uses for unfair competitive advantage). So I added to my parser option -r to dump instructions with unrecognized attributes, and also wrote little perl script to collect them. Then run whole pipe like

../ptx.parse/tp -r < ptx.txt | perl ../ptx.parse/ra.pl

And try to guess what happened? Yes - nvidia uses ~5-7% of instructions with undocumented attributes

пятница, 11 сентября 2026 г.

shrinking holes in SASS allocated registers

In my previous post I discovered that SASS register allocation can have holes - non-contiguous register indices allocated to a function where intermediate registers like R57 remain unused despite R56 and R58 being live/allocated

To estimate size of problem I wrote some code to collect statistics about number (and share) of functions having holes in register allocations:

  • libcublas.so.13.7.0.74.sm_90.cubin
    ; 34 holes in regs (784), 0.043367
    ; 3 holes in uregs (434), 0.006912
    ; 34 functions with holes (0.629630 from total)
  • libcublas.so.13.7.0.608.sm_90.cubin
    ; 41 holes in regs (8546), 0.004798
    ; 15 holes in uregs (2784), 0.005388
    ; 39 functions with holes (0.102632 from total)
  • libcublas.so.13.7.0.935.sm_90.cubin
    ; 19 holes in regs (5016), 0.003788
    ; 17 holes in uregs (2012), 0.008449
    ; 11 functions with holes (0.059783 from total)

As you can see holes occupy up to 4% of total registers (in average 0.3-0.5%) and it's very tempting to try reduce them. It would seem—what could be simpler? If we have something like that
; RHoles max 61: R56 R58

just remap in whole function R61 to R58 and R60 to R56, and then reduce EIATTR_REGCOUNT,right? Well, actually no