Skip to main content
emphasise that the shift is 1 bit
Source Link
ecm
  • 196
  • 6

Below your label now_rotate you shift the single bit into a mask to OR into the destination register. I shift the destination register by one place in each iteration instead. Your way uses an additional register -- this is of course more of an issue for my 16-bit implementation that runs out of registers sooner. The additional shifting of the single bit into a mask, needing an up-to-31-places shift, and applying that mask to the destination would also be much more complex with 16-bit registers than my solution's double-width one-bit shift.

Below your label now_rotate you shift the single bit into a mask to OR into the destination register. I shift the destination register by one place in each iteration instead. Your way uses an additional register -- this is of course more of an issue for my 16-bit implementation that runs out of registers sooner. The additional shifting of the single bit into a mask, needing an up-to-31-places shift, and applying that mask to the destination would also be much more complex with 16-bit registers than my solution's double-width shift.

Below your label now_rotate you shift the single bit into a mask to OR into the destination register. I shift the destination register by one place in each iteration instead. Your way uses an additional register -- this is of course more of an issue for my 16-bit implementation that runs out of registers sooner. The additional shifting of the single bit into a mask, needing an up-to-31-places shift, and applying that mask to the destination would also be much more complex with 16-bit registers than my solution's double-width one-bit shift.

fix: only conditional branches need to be predicted; comment on spaghetti code; inc and xor for shorter code; note that the mask operation uses one more register; note that masking is more complex for 16-bit
Source Link
ecm
  • 196
  • 6

Both of my loops only have one branch for the loop itself each, whereas your loop's code branches quite a lot, including a conditional branch. BranchConditional branch prediction is a notoriously difficult part of the processor, so less branches are better. (Other answers also reducedeliminated the amount of branchesconditional branch in several ways.)

Aside the conditional branch, you're also using an unconditional branch to now_rotate. This does not have the negative performance impact of a conditional branch, but it does make your solution be spaghetti code. The amount of labels you need (add_one) is another sign of spaghetti code. Here's how to reduce the spaghetti factor (while keeping the conditional branch):

        shr eax, 0x1    ; Kick the right-most bit out ...
        mov ebx, 0x0    ; Initialise register to a 0
        jnc now_rotate  ; If eax bit was a 0 jump to now_rotate
        mov ebx, 0x1    ; Reset register to a 1
now_rotate:

This streamlines the control flow. However, if ebx is initialised to zero then there is an easier way to get it to one. inc ebx is (in 32-bit code segments) a one-byte opcode:

        shr eax, 0x1    ; Kick the right-most bit out ...
        mov ebx, 0x0    ; Initialise register to a 0
        jnc now_rotate  ; If eax bit was a 0 jump to now_rotate
        inc ebx         ; Increment register to a 1
now_rotate:

And it is yet easier to initialise to zero by using "xor with itself", which has a shorter encoding than mov ebx, imm32, and is recognised as a zeroing idiom on modern processors. This clobbers the flags register, which means we cannot put it between the shift and the conditional jump. But we can swap the shift and zeroing to have the zero initialisation first:

        xor ebx, ebx    ; Initialise register to a 0
        shr eax, 0x1    ; Kick the right-most bit out ...
        jnc now_rotate  ; If eax bit was a 0 jump to now_rotate
        inc ebx         ; Increment register to a 1
now_rotate:

Below your label now_rotate you shift the single bit into a mask to OR into the destination register. I shift the destination register by one place in each iteration instead. ThisYour way uses an additional register -- this is of course more a matter of taste. However,an issue for themy 16-bit implementation that runs out of registers sooner. The additional shifting of the single bit into a mask, needing an up-to-31-places shift, and applying that mask to the destination would also be much more complex with 16-bit registers than my solution's double-width shift.

Both loops only have one branch for the loop itself each, whereas your loop's code branches quite a lot. Branch prediction is a notoriously difficult part of the processor, so less branches are better. (Other answers also reduced the amount of branches in several ways.)

Below your label now_rotate you shift the single bit into a mask to OR into the destination register. I shift the destination register by one place in each iteration instead. This is more a matter of taste. However, for the 16-bit implementation shifting the single bit into a mask and applying that mask to the destination would be more complex than my solution's double-width shift.

Both of my loops only have one branch for the loop itself each, whereas your loop's code branches quite a lot, including a conditional branch. Conditional branch prediction is a notoriously difficult part of the processor, so less branches are better. (Other answers also eliminated the conditional branch in several ways.)

Aside the conditional branch, you're also using an unconditional branch to now_rotate. This does not have the negative performance impact of a conditional branch, but it does make your solution be spaghetti code. The amount of labels you need (add_one) is another sign of spaghetti code. Here's how to reduce the spaghetti factor (while keeping the conditional branch):

        shr eax, 0x1    ; Kick the right-most bit out ...
        mov ebx, 0x0    ; Initialise register to a 0
        jnc now_rotate  ; If eax bit was a 0 jump to now_rotate
        mov ebx, 0x1    ; Reset register to a 1
now_rotate:

This streamlines the control flow. However, if ebx is initialised to zero then there is an easier way to get it to one. inc ebx is (in 32-bit code segments) a one-byte opcode:

        shr eax, 0x1    ; Kick the right-most bit out ...
        mov ebx, 0x0    ; Initialise register to a 0
        jnc now_rotate  ; If eax bit was a 0 jump to now_rotate
        inc ebx         ; Increment register to a 1
now_rotate:

And it is yet easier to initialise to zero by using "xor with itself", which has a shorter encoding than mov ebx, imm32, and is recognised as a zeroing idiom on modern processors. This clobbers the flags register, which means we cannot put it between the shift and the conditional jump. But we can swap the shift and zeroing to have the zero initialisation first:

        xor ebx, ebx    ; Initialise register to a 0
        shr eax, 0x1    ; Kick the right-most bit out ...
        jnc now_rotate  ; If eax bit was a 0 jump to now_rotate
        inc ebx         ; Increment register to a 1
now_rotate:

Below your label now_rotate you shift the single bit into a mask to OR into the destination register. I shift the destination register by one place in each iteration instead. Your way uses an additional register -- this is of course more of an issue for my 16-bit implementation that runs out of registers sooner. The additional shifting of the single bit into a mask, needing an up-to-31-places shift, and applying that mask to the destination would also be much more complex with 16-bit registers than my solution's double-width shift.

improve review by relating to the question's exercise and code
Source Link
ecm
  • 196
  • 6

In relation to your exercise, this solution allows dynamically selecting the width of the bit mirror operation, so that the 8-bit example "if x == {01001111}_2, then the output is {11110010}_2" can be run by inputting 8 in bx:dx and 0100_1111b = 4Fh in the variable.

I also decided to mask out the uninvolved (higher) bits and return them unchanged.

Both loops only have one branch for the loop itself each, whereas your loop's code branches quite a lot. Branch prediction is a notoriously difficult part of the processor, so less branches are better. (Other answers also reduced the amount of branches in several ways.)

Below your label now_rotate you shift the single bit into a mask to OR into the destination register. I shift the destination register by one place in each iteration instead. This is more a matter of taste. However, for the 16-bit implementation shifting the single bit into a mask and applying that mask to the destination would be more complex than my solution's double-width shift.

Your loop also shifts the source register to the right one place in each iteration, shifting a bit of interest into the Carry Flag. This is the same as in my solution. It would also be valid to shift to the left and shift the MSB into the Carry Flag, then rotate-with-carry-right that into the MSB of the destination register. The way we chose is easier to handle for arbitrary width bit mirroring though.


In relation to your exercise, this solution allows dynamically selecting the width of the bit mirror operation, so that the 8-bit example "if x == {01001111}_2, then the output is {11110010}_2" can be run by inputting 8 in bx:dx and 0100_1111b = 4Fh in the variable.

I also decided to mask out the uninvolved (higher) bits and return them unchanged.

Both loops only have one branch for the loop itself each, whereas your loop's code branches quite a lot. Branch prediction is a notoriously difficult part of the processor, so less branches are better. (Other answers also reduced the amount of branches in several ways.)

Below your label now_rotate you shift the single bit into a mask to OR into the destination register. I shift the destination register by one place in each iteration instead. This is more a matter of taste. However, for the 16-bit implementation shifting the single bit into a mask and applying that mask to the destination would be more complex than my solution's double-width shift.

Your loop also shifts the source register to the right one place in each iteration, shifting a bit of interest into the Carry Flag. This is the same as in my solution. It would also be valid to shift to the left and shift the MSB into the Carry Flag, then rotate-with-carry-right that into the MSB of the destination register. The way we chose is easier to handle for arbitrary width bit mirroring though.

Source Link
ecm
  • 196
  • 6
Loading