BT reg,reg instructions retain full width index dependencies after partial register writes

bt64_reg:
    mov al, cl
    bt rdx, rax
    setc al
    movzx eax, al
    ret

At MMAT_GENERATED:

; USE: ah.1,rax^2.6,rdx.8,cl.1
; DEF: cf.1,rax.8

mov cl.1, al.1 ; u=cl.1 d=al.1
mov call !_bittest64<fast:"const __int64 *" &(rdx.8)<8/0>.8,"unsigned __int64" rax.8>
    => "unsigned __int8" .1, cf.1 ; u=rax.16,(ALLMEM) d=cf.1

At MMAT_LVARS:

; USE: ah.1,rax^2.6,rdx.8,cl.1,(GLBLOW,GLBHIGH)
; DEF: rax.8

mov cl0.1{1}, rax0.1{1} ; u=cl.1 d=al.1
xdu call !_bittest64<fast:"const __int64 *" &(rdx0.8)<8/0>.8,"unsigned __int64" rax0.8>
    => "unsigned __int8" .1, result.8 ; u=rax.16,(GLBLOW,GLBHIGH) d=rax.8
__int64 __fastcall bt64_reg(char a1, __int64 a2)
{
  unsigned __int64 v2; // rax

  LOBYTE(v2) = a1;
  return _bittest64(&a2, v2);
}

For the register form bt rdx, rax, the bit index is RAX & 63, therefore only the low six bits of AL can affect the result after mov al, cl.

The expected recovery is:

return _bittest64(&a2, (unsigned __int8)a1);
// or 
return (a2 >> ((unsigned __int8)a1 & 63)) & 1;

For comparison, the explicitly zero extended variant (using movzx eax, cl) is recovered to _bittest64(&a2, (unsigned __int8)a1), which makes me think this is just an issue with the simplification for partial registers in this case. Who knows.

I will also note that the BTS/BTR/BTC C translations are UB, but this is more of a complaint about semantics.

Thanks for the thorough write-up! We’ll take a closer look and come back in this thread when we have more specific updates.

It could be argued that _bittest translates to bt and adding a cast would be unjustified extra work, but ok, we added it.

Thanks for the update.

Just to clarify, I don’t particularly care whether the output has a cast, and I agree that the _bittest helper does imply bt.

My concern was ah.1,rax^2.6 remaining live through MMAT_LVARS because the helper consumes the full rax.8, even though those incoming bits cannot affect the result of bt rdx, rax after mov al, cl.

That is what produces the partially initialized v2 in the original example. The expected pseudocode was meant to illustrate eliminating that dependency, not that I wanted to request a particular spelling.

Does the change remove those upper register dependencies in the microcode as well? If so, that addresses what I was reporting.

Normally there are no wrong live registers now.

I understand that you are ok with an extra cast but some users may disagree.