Search code examples
assemblyx86low-levelcarryflag

Assembly - SHL instruction doesn't turn on carry flag


I have these lines of code that are supposed to set carry flag to 1. I ran this code through emulator in emu8086 and it showed me that carry flag is not set. In my opinion, carry flag should be set beacuse number 0xC7 (in binary: 1100 0111) is moved to left, so carry flag should return 1. Hope that someone can help.

.model small

.data

.code

main proc
mov ax, 0xC7          
xor cl , cl 
shl ax,1 
jnc a1 
inc cl
a1:
shl ax,1 
jnc a2 
inc cl
a2:
shl ax,1 
jnc a3 
inc cl
a3:
shl ax,1 
jnc a4 
inc cl
a4:

endp

end main

Solution

  • So you want to do a 16-bit shift but have CF set from the top of the low half? I don't know why you want that, especially if you're going to jnc instead of using adc cl, 0 to do CL += CF.

    There are more efficient ways to popcnt the high nibble of a register, e.g. make a copy of the register and actually shift bits out instead of just withing a register. Or use the popcnt instruction after isolating those bits in a register, if you can assume a modern CPU.


    Anyway, there's no single instruction for that, but there are a couple options for setting CF according to a bit, depending on what CPU compatibility you need. The most straightforward is:

    ;; 386 for BT/BTS
       shl   ax, 1
       bt    ax, 8        ; set CF from the bit that was previously the top of AL
    
       adc   cl, 0        ; CL += CF
    

    There's also this way that's probably slow on old CPUs: x86 shifts/rotates mask the count with &31 (or &63 for 64-bit shifts) so shifts/rotates narrower than 32-bit can use a count as large as the operand-size and have it not count as 0.

    rol sets CF according to the last bit that "wrapped around" from high to low. A count of 8 for an 8-bit register leaves it unmodified, with the last bit rotating around being the low bit. Also, CPUs before 286 didn't mask the count anyway, they just took as many clock cycles as the shift count. (So this is pretty slow on old CPUs without a barrel shifter for constant-time rotates.)

    Or if the Operation section of the manual reflects the actual 286 implementation, rotates do mod size for the actual shifting work, but the count != 0 check to see if it sets FLAGS at all is based on count & 1Fh not count mod 8. And if it does, CF = LSB(dest)

    ;; 186 for immediate-count shifts/rotates
       shl   ax, 1
       rol   ah, 8        ; set CF from the bit that was previously the top of AL
    
       adc   cl, 0        ; CL += CF
    

    Obviously it's easier to set ZF according to a specific bit, e.g.

       add   ax, ax     ; AX <<= 1
       test  ah, 1      ; ZF = low bit of AH
    
       ;test  ax, 1<<8   ; ZF = low bit of AH   same code size, actually
       jz   no_increment
       inc   cl
    no_increment:
    

    But branching sucks vs. adc.

    Note that shl by 1 has basically no advantages except you can use it with a register destination. On some CPUs it's more efficient to use add ax,ax to left-shift. It even sets CF the same way (according to what was previously the top bit).