assembly - procesador - Cómo mover inmediatos de 128 bits a registros XMM
sse wikipedia (5)
Como una de las 10000 formas de hacerlo, use SSE4.1 pinsrq
mov rax, first half
movq xmm0, rax ; better than pinsrq xmm0,rax,0 for performance and code-size
mov rax, second half
pinsrq xmm0, rax, 1
Ya hay una pregunta sobre esto, pero fue cerrada como "ambigua", así que estoy abriendo una nueva. He encontrado la respuesta, tal vez también ayude a otros.
La pregunta es: ¿cómo se escribe una secuencia de código de ensamblaje para inicializar un registro XMM con un valor inmediato (constante) de 128 bits?
Hay varias formas de incrustar constantes en el flujo de instrucciones:
- utilizando operandos inmediatos
- cargando desde direcciones relativas a PC
Entonces, si bien no hay manera de realizar una carga inmediata en un registro XMM
, es posible realizar una carga relativa a la PC (en 64 bits) desde un valor almacenado "justo al lado" hasta donde se ejecuta el código. Eso crea algo como:
.align 4
.val:
.long 0x12345678
.long 0x9abcdef0
.long 0xfedbca98
.long 0x76543210
func:
movdqa .val(%rip), %xmm0
Cuando desmonte:
0000000000000000 : 0: 78 56 34 12 f0 de bc 9a 8: 98 ca db fe 10 32 54 76 0000000000000010 : 10: 66 0f 6f 05 e8 ff ff movdqa -0x18(%rip),%xmm0 # 0
que es totalmente compacto , de 23 bytes.
Otras opciones son construir el valor en la pila y volver a cargarlo desde allí. En 32bit x86, donde no tiene %rip
acceso relativo a la memoria, todavía se puede hacer eso en 24 bytes (asumiendo que el apilador está alineado en la entrada; de lo contrario, se requiere una carga no alineada):
00000000 : 0: 68 78 56 34 12 push $0x12345678 5: 68 f0 de bc 9a push $0x9abcdef0 a: 68 98 ca db fe push $0xfedbca98 f: 68 10 32 54 76 push $0x76543210 14: 66 0f 6f 04 24 movdqa (%esp),%xmm0
Mientras que en 64 bits (la alineación del punto de pila en la entrada de la función está garantizada por el ABI), tomaría 27 bytes:
0000000000000000 : 0: 48 b8 f0 de bc 9a 78 56 34 12 movabs $0x123456789abcdef0,%rax a: 50 push %rax b: 48 b8 10 32 54 76 98 ba dc fe movabs $0xfedcba9876543210,%rax 15: 50 push %rax 16: 66 0f 6f 04 24 movdqa (%rsp),%xmm0
Si compara alguno de estos con la versión MOVLHPS
, notará que es el más largo:
0000000000000000 : 0: 48 b8 f0 de bc 9a 78 56 34 12 movabs $0x123456789abcdef0,%rax a: 66 48 0f 6e c0 movq %rax,%xmm0 f: 48 b8 10 32 54 76 98 ba dc fe movabs $0xfedcba9876543210,%rax 19: 66 48 0f 6e c8 movq %rax,%xmm1 1e: 0f 16 c1 movlhps %xmm1,%xmm0
a los 33 bytes.
La otra ventaja de cargar directamente desde la memoria de instrucciones es que el movdqa
no depende de nada anterior. Lo más probable es que la primera versión, dada por @Paul R, sea la más rápida que pueda obtener.
La mejor solución (especialmente si desea mantener el SSE2, es decir, evitar el uso de AVX) para inicializar dos registros (por ejemplo, xmm0 y xmm1) con las dos mitades de 64 bits de su valor inmediato, haga MOVLHPS xmm0, xmm1 para: inicialice un valor de 64 bits, la solución más sencilla es usar un registro de propósito general (por ejemplo, AX) y luego usar MOVQ para transferir su valor al registro XMM. Entonces la secuencia sería algo como esto:
MOV RAX, <first_half>
MOVQ XMM0, RAX
MOV RAX, <second_half>
MOVQ XMM1, RAX
MOVLHPS XMM0,XMM1
Solo quería agregar que se puede leer acerca de cómo generar varias constantes usando ensamblaje en el manual de Agner Fog Optimización de subrutinas en lenguaje ensamblador , Generando constantes, sección 13.4, página 121.
Puedes hacerlo así, con solo una instrucción de movaps
:
.section .rodata # put your constants in the read-only data section
.p2align 4 # align to 16 = 1<<4
LC0:
.long 1082130432
.long 1077936128
.long 1073741824
.long 1065353216
.text
foo:
movaps LC0(%rip), %xmm0
Por lo general, es preferible cargarlo con una carga de datos que incrustarlo en el flujo de instrucciones, especialmente debido a la cantidad de instrucciones que toma. Eso es varios uops adicionales para que la CPU los ejecute, para una constante arbitraria que no puede ser generada por todos con un par de turnos.
Si es más fácil, puede poner constantes justo antes o después de una función que compiló, en lugar de en una sección separada. Pero como las CPU han dividido los cachés L1d / L1i y los TLB, generalmente es mejor agrupar las constantes por separado de las instrucciones.
Si ambas mitades de su constante son las mismas, puede transmitirlas con SSE3.
movddup (m64), %xmm0
.