3

I am playing with a bit of SSE assembly code in which I do not have enough xmm registers to keep all the temporary results and useful constants in registers at the same time.

As a workaround, for some constant vectors that have identical components, I “compress” several vectors into a single xmm register, xmm14 below. I use the pshufd instruction to decompress the constant vector I need. This instruction has a bit of latency, but since it takes a source and a destination register, it is otherwise very convenient:

…
Lfour_15_9:
    .long 4
    .long 1549556828
    .long 909522486
    .long 0
…
    movdqa  Lfour_15_9(%rip), %xmm14
…
    pshufd  $0, %xmm14, %xmm4
    paddd   %xmm4, %xmm3
…
    pshufd  $0b10101010, %xmm14, %xmm5
…
    pshufd  $0b10101010, %xmm14, %xmm5
…
    pshufd  $0b01010101, %xmm14, %xmm5
    xorps   %xmm5, %xmm2    
    movaps  %xmm5, 112(%rax)

The above code is in gas/AT&T syntax and I am targeting Intel processors from Core 2 to Westmere, that offer instructions up to SSSE3.

One of Agner Fog's manuals points out that for some uses, it may be advantageous to use vector instructions that have the wrong “type”. For instance, memcpy is advantageous to write with movaps instructions even if the data being moved is not floating-point because movaps is shorter than movdqa, is available on more processors, and since it does not compute with the data, none of the usual caveats about subnormals apply. The same advice is given for shuffling words around (sections 13.2 and 13.3 in the manual I linked to earlier).

My case is a bit special because of the constant vectors I aim to reconstitute, some can, if necessary, be used with only single-precision “type” instructions: these will be involved only in movaps, shufps, xorps computations. And some constant vectors will have to participate into computations that can only be done with integer-type instructions: paddd (and thus I can use movdqa, pshufd and pxor instructions as necessary to remain in the integer execution domain).

The general version of this question is: considering that I am targeting Intel processors between Core 2 and Westmere, what types of instructions should I use respectively to (re-)load xmm14 from memory, to uncompress it to a register that will only see single-precision computations, to uncompress it to a register that will see some computations that cannot be done with single-precision instructions, and for those operations that can be done with single-precision instructions in the latter case?


EDIT: The part of the question below this point was answered by harold in a comment.


And a more specific sub-question that's included in the general question: does anyone have an explanation for why, when I randomly replace some integer execution domain instructions by floating-point instructions (e.g. movdqa instructions by movaps instructions), the function can compute wrong? I expected the only consequence would be execution delays, not wrong results.

For instance, if in the above I change only the pshufd $0, %xmm14, %xmm4 instruction to a shufps one, the computations become completely wrong (xmm4 is the register that is involved in a paddd later). Changing other instructions instead of that one result in other kinds of errors.

Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281
  • 1
    You can't write `pshufd $0, %xmm14, %xmm4` as a `shufps`. The lowest two floats will come from the destination. – harold Feb 22 '15 at 17:35
  • @harold Oh. I think that answers the question about correctness. Now the question feels silly (I only asked because I thought at least the correctness aspect was interesting). – Pascal Cuoq Feb 22 '15 at 17:46
  • Did the `movdqa` -> `movaps` change also break something? That would be more interesting, because it really shouldn't – harold Feb 22 '15 at 17:49
  • @harold No, I have not seen it break anything. I was really confused because the `pshufd` -> `shufps` change sometimes worked (in other instances where source and destination were the same). – Pascal Cuoq Feb 22 '15 at 17:51

1 Answers1

3

Prefer integer-domain instructions for things like xor. On Intel CPUs, only one execution port can handle FP-domain logicals (XORPS, etc.), but most of the execution units (On SnB to Haswell: p015, but not Haswell's port 6) can handle vector integer logical instructions (PAND/POR/PXOR).

Sometimes it costs an extra 1 cycle of latency if the result of a FP-domain instruction is needed an an input to an vector-int-domain instruction, according to Agner Fog's testing. (See the microarchitecture docs). This applies to AMD and Intel. This only matters if the instruction is on the critical path. (longest dep chain in the loop).

Correctness isn't an issue, except as you found when the non-orthogonality of the instructions trips you up. shufps doesn't do the same thing as pshufd. vpermilps ymm, ymm, imm does do the same thing as pshufd, I think, and seems to only have been introduced so you can combine a shuffle with a load from memory. (Otherwise you could just use the AVX version of shufps with the same register as both sources, and get the same behaviour).

IDK if anyone's thoroughly tested all the cases where there's no extra latency for using the shorter-instruction-encoding ...ps versions of things. The uop cache in SnB and later Intel CPUs makes that less of an issue for inner loops, though. (Instruction decoding is only a bottleneck the first time through a loop.)

edit: except for uop-cacheline boundaries, which can be the bottleneck if your code could otherwise sustain the full 4 uops / cycle. IDK if there are any tools for helping align x86 instructions so the uop cache lines hold multiples of 4 uops.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Update: Skylake runs FP logicals on any port, with bypass forwarding latency depending on which port actually gets picked. – Peter Cordes Jun 27 '20 at 04:11