|
| 1 | +# About |
| 2 | + |
| 3 | +There are packed variants for most scalar bitwise operations. |
| 4 | +They follow the same general syntax as [SIMD integer operations][simd-integers], but they have no size suffix: |
| 5 | + |
| 6 | +| instruction | description | |
| 7 | +|-------------|-----------------------------------| |
| 8 | +| `pand` | bitwise AND of two 128-bit values | |
| 9 | +| `por` | bitwise OR of two 128-bit values | |
| 10 | +| `pxor` | bitwise XOR of two 128-bit values | |
| 11 | + |
| 12 | +Note that there is no packed `NOT`. |
| 13 | +However, there is an ANDN operation, which negates the **destination** operand and then calculates the AND between both operands. |
| 14 | +It can be thought of as a NOT combined with an AND: |
| 15 | + |
| 16 | +| instruction | description | |
| 17 | +|-------------|------------------------------------------------------------| |
| 18 | +| `pandn` | bitwise AND of the **negated** destination with the source | |
| 19 | + |
| 20 | +Note that a bitwise operation combines bits at the same position in two different operands, so lane boundaries cannot change the result. |
| 21 | +This is why these instructions do not have a size suffix, the register behaves as a single 128-bit lane: |
| 22 | + |
| 23 | +```x86asm |
| 24 | +pand xmm0, xmm1 ; xmm0 = xmm0 AND xmm1 |
| 25 | +por xmm2, xmm3 ; xmm2 = xmm2 OR xmm3 |
| 26 | +pxor xmm4, xmm5 ; xmm4 = xmm4 XOR xmm5 |
| 27 | +pandn xmm6, xmm7 ; xmm6 = (NOT xmm6) AND xmm7 |
| 28 | +``` |
| 29 | + |
| 30 | +~~~~exercism/note |
| 31 | +All instructions in this concept compute bits but set no flags. |
| 32 | +A later concept will address conditionals with packed values. |
| 33 | +~~~~ |
| 34 | + |
| 35 | +## Per-Lane Shifts |
| 36 | + |
| 37 | +Shifts, unlike the pure bitwise operations, do move bits across positions, so they need a lane size to set a boundary. |
| 38 | +Within each lane they behave like their scalar counterparts, and bits never cross from one lane into a neighbor: |
| 39 | + |
| 40 | +| instruction | description | |
| 41 | +|---------------------------|-------------------------------------| |
| 42 | +| `psllw`, `pslld`, `psllq` | shift left logical, per lane | |
| 43 | +| `psrlw`, `psrld`, `psrlq` | shift right logical, per lane | |
| 44 | +| `psraw`, `psrad` | shift right arithmetic, per lane | |
| 45 | + |
| 46 | +Note that these instructions follow the syntax for packed integer operations, including a size suffix. |
| 47 | +There is no byte shift, only word, dword and, with the exception of shift right arithmetic, qword. |
| 48 | + |
| 49 | +One difference between packed shifts and their scalar counterparts is in the operation name: |
| 50 | + |
| 51 | +- Instead of `shl` and `shr`, we have `sll` and `srl`, for **s**hift **l**eft (**r**ight) **l**ogical. |
| 52 | +- Instead of `sar`, we have `sra`, for **s**hift **r**ight **a**rithmetic. |
| 53 | + |
| 54 | +Although the name is different, the operation is the same: |
| 55 | + |
| 56 | +- A logical right shift fills the uppermost bits with zero. |
| 57 | + It is equivalent to an unsigned division by a power of two. |
| 58 | +- An arithmetic right shift fills the uppermost bits with the value of the sign bit. |
| 59 | + It is equivalent to a signed division by a power of two, rounding toward negative infinity. |
| 60 | +- A shift to the left always fills with zero (this is why there is no need for a shift left arithmetic). |
| 61 | + |
| 62 | +The shift count is the same for every lane. |
| 63 | +It can be an immediate, or the low 64 bits of an `xmm` register: |
| 64 | + |
| 65 | +```x86asm |
| 66 | +pslld xmm0, 4 ; each 32-bit lane shifted left by 4 |
| 67 | +psrlw xmm1, 1 ; each 16-bit lane shifted right by 1, zero-filled |
| 68 | +psrad xmm2, xmm3 ; each 32-bit lane shifted right by the count in xmm3, sign-filled |
| 69 | +``` |
| 70 | + |
| 71 | +~~~~exercism/note |
| 72 | +A shift count larger than the lane width does not wrap around like the scalar `shl`/`shr` family. |
| 73 | +It simply produces zero (or all sign bits, for shift right arithmetic). |
| 74 | +~~~~ |
| 75 | + |
| 76 | +## Floating-Point Bitwise Operations |
| 77 | + |
| 78 | +Each bitwise instruction also has float-named twins, following the same syntax we have seen for packed floating-point operations: |
| 79 | + |
| 80 | +| integer name | float names | |
| 81 | +|--------------|----------------------| |
| 82 | +| `pand` | `andps`, `andpd` | |
| 83 | +| `pandn` | `andnps`, `andnpd` | |
| 84 | +| `por` | `orps`, `orpd` | |
| 85 | +| `pxor` | `xorps`, `xorpd` | |
| 86 | + |
| 87 | +Since bitwise operations act on bits, in practice integer and floating-point operations compute the same result. |
| 88 | +The different names exist for two reasons: |
| 89 | + |
| 90 | +1. Documenting the intent, in the same way as we would prefer `movdqa`, `movaps` or `movapd` according to the nature of the bytes being copied. |
| 91 | +2. Integer and floating-point operations often have different execution domains. |
| 92 | + Operations within the same execution domain can usually be sequenced faster. |
| 93 | + |
| 94 | +There is no shift operation for floating-point values. |
| 95 | + |
| 96 | +## Floating-Point Bit Representation |
| 97 | + |
| 98 | +An [IEEE 754][ieee-754] floating-point number has a very different bit representation from an integer. |
| 99 | + |
| 100 | +In an unsigned integer, each bit has a uniform meaning: it represents a power of two corresponding to the bit index. |
| 101 | +The integer is the sum of the powers of two corresponding to the set bits. |
| 102 | +This is why shifting the bits can multiply or divide the number by a power of two: we are just changing the index of each set bit. |
| 103 | + |
| 104 | +In a signed integer, most bits are interpreted in the same way as in an unsigned integer. |
| 105 | +However, the uppermost bit is special. |
| 106 | +If it is set, instead of being added to the others, we have to subtract the corresponding power of two, which results in a negative number. |
| 107 | +In practice, to change the sign of a number, we would conceptually need two steps: flipping all bits (a `not`) and then adding 1 to the result (an `inc`). |
| 108 | + |
| 109 | +Bits in floating-point numbers do not have a uniform meaning and they need to be interpreted in very different ways according to their position: |
| 110 | + |
| 111 | +| field | indexes (32-bit) | indexes (64-bit) | |
| 112 | +|----------|------------------|------------------| |
| 113 | +| sign | 31 | 63 | |
| 114 | +| exponent | 30-23 | 62-52 | |
| 115 | +| fraction | 22-0 | 51-0 | |
| 116 | + |
| 117 | +The resulting number is calculated by `(-1)^sign * 1.fraction * 2^(exponent - bias)`. |
| 118 | +The bias depends on the precision: |
| 119 | + |
| 120 | +| size | bias | |
| 121 | +|--------|------| |
| 122 | +| 32-bit | 127 | |
| 123 | +| 64-bit | 1023 | |
| 124 | + |
| 125 | +In both formats, the sign of the number is exclusively determined by the sign bit. |
| 126 | +Changing the sign of a floating-point number is achieved by just flipping this bit (an XOR operation). |
| 127 | +Getting the absolute value is achieved by just clearing it (an AND where the bit is cleared): |
| 128 | + |
| 129 | +```x86asm |
| 130 | +section .rodata |
| 131 | +align 16 |
| 132 | +abs_mask: dd 0b0_11111111_11111111111111111111111, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF ; same number in binary and hexadecimal |
| 133 | +sign_mask: dd 0b1_00000000_00000000000000000000000, 0x80000000, 0x80000000, 0x80000000 ; same number in binary and hexadecimal |
| 134 | +
|
| 135 | +section .text |
| 136 | +fn: |
| 137 | + andps xmm0, [rel abs_mask] ; absolute value: clear the sign bit of all 4 floats |
| 138 | + xorps xmm1, [rel sign_mask] ; negation: flip the sign bit of all 4 floats |
| 139 | + ... |
| 140 | +``` |
| 141 | + |
| 142 | +Note that in this bit representation, shifting bits does not have a predictable and uniform result. |
| 143 | +The fraction can "spill" into the exponent and the exponent, into the sign. |
| 144 | + |
| 145 | +However, it is sometimes convenient to isolate the different parts of the representation to operate on them. |
| 146 | + |
| 147 | +For example, the bits in the exponent should first be interpreted as an unsigned number, before the bias is subtracted from it. |
| 148 | +This means we can produce a 32-bit or 64-bit integer whose underlying bit representation corresponds to some power of two in the corresponding floating-point format. |
| 149 | +Then this raw bit representation can be used to multiply or divide some other floating-point number. |
| 150 | + |
| 151 | +For example: |
| 152 | + |
| 153 | +```x86asm |
| 154 | +mov eax, 210 ; in the exponent position, this would be 2^(210 - 127) = 2^83 in a 32-bit floating-point number |
| 155 | +shl eax, 23 ; the bits are now in the correct position for the exponent of a 32-bit floating-point number |
| 156 | +movd xmm0, eax ; we move the raw bits to xmm0 |
| 157 | +mulss xmm1, xmm0 ; we are multiplying a 32-bit floating-point number in xmm1 by 2^83 |
| 158 | +``` |
| 159 | + |
| 160 | +Conversely, we could extract the exponents of packed floating-point numbers by performing an AND with a mask where the exponent bits are set and all others are cleared. |
| 161 | +Combined with a shift and a subtraction, this produces the unbiased exponent of every lane as a signed integer: |
| 162 | + |
| 163 | +```x86asm |
| 164 | +section .rodata |
| 165 | +align 16 |
| 166 | +exp_mask: dd 0b0_11111111_00000000000000000000000, 0x7F800000, 0x7F800000, 0x7F800000 ; same number in binary and hexadecimal |
| 167 | +bias: dd 127, 127, 127, 127 |
| 168 | +
|
| 169 | +section .text |
| 170 | +fn: |
| 171 | + movdqa xmm0, [rel values] ; 4 packed 32-bit floating-point numbers |
| 172 | + pand xmm0, [rel exp_mask] ; keep bits 30-23 |
| 173 | + psrld xmm0, 23 ; slide the exponent field to the bottom of each lane |
| 174 | + psubd xmm0, [rel bias] ; remove the bias |
| 175 | + ... |
| 176 | +``` |
| 177 | + |
| 178 | +~~~~exercism/caution |
| 179 | +Operating on the raw bits of floating-point numbers, while useful in some situations, should be done with caution. |
| 180 | +The formula above only describes **normal** numbers: an exponent field of all zeros encodes zero and the denormal numbers, and a field of all ones encodes the infinities and NaN. |
| 181 | +Adding to, or subtracting from, the exponent field is only safe if there is no over or underflow and if the input number is normal. |
| 182 | +Also, adding a valid `x` to the exponent field of `0.0` results in `2^(x - bias)`, not in `0.0 * 2^x`. |
| 183 | +~~~~ |
| 184 | + |
| 185 | +~~~~exercism/note |
| 186 | +The [Float Toy page][toy] has a nice, graphical explanation for how a floating-point numbers' bits are converted to an actual floating-point value. |
| 187 | +
|
| 188 | +[toy]: https://evanw.github.io/float-toy |
| 189 | +~~~~ |
| 190 | + |
| 191 | +## AVX and Beyond |
| 192 | + |
| 193 | +AVX gives every instruction here a `v`-prefixed three-operand form on wider registers, leaving the behavior unchanged: |
| 194 | + |
| 195 | +```x86asm |
| 196 | +vpand ymm0, ymm1, ymm2 ; ymm0 = ymm1 AND ymm2, 256 bits |
| 197 | +vxorps ymm3, ymm4, ymm5 ; ymm3 = ymm4 XOR ymm5, 256 bits |
| 198 | +vpsrad ymm6, ymm7, 2 ; eight 32-bit lanes shifted right arithmetically |
| 199 | +``` |
| 200 | + |
| 201 | +AVX-512 closes both gaps left open by SSE: |
| 202 | + |
| 203 | +- `vpsraq` finally provides the 64-bit arithmetic right shift. |
| 204 | +- `vpternlogd` and `vpternlogq` compute **any** three-input bitwise function in one instruction. |
| 205 | + The 8-bit immediate is the truth table itself: bit `i` of the immediate is the output for input combination `i`. |
| 206 | + Every chain of `pand`/`pandn`/`por`/`pxor` over three operands collapses into a single `vpternlog`. |
| 207 | + |
| 208 | +These extensions are mentioned for completeness. |
| 209 | +The exercises in this track use the SSE forms. |
| 210 | + |
| 211 | +~~~~exercism/note |
| 212 | +A full reference for every instruction mentioned here is available in the [x86 instruction reference][instruction-reference]. |
| 213 | +
|
| 214 | +[instruction-reference]: https://www.felixcloutier.com/x86/ |
| 215 | +~~~~ |
| 216 | + |
| 217 | +[simd-integers]: https://exercism.org/tracks/x86-64-assembly/concepts/simd-integers |
| 218 | +[ieee-754]: https://en.wikipedia.org/wiki/IEEE_754 |
0 commit comments