x64 - microsoft visual c++ 2017
Visual C++ x64 agregar con llevar (3)
Dado que no parece haber un intrínseco para ADC y no puedo usar el ensamblador en línea para la arquitectura x64 con Visual C ++, ¿qué debo hacer si quiero escribir una función usando agregar con acarreo pero incluirlo en un espacio de nombres de C ++?
(Emular con operadores de comparación no es una opción. Este complemento de 256 megabits es crítico para el rendimiento).
VS2010 tiene soporte integrado para compilar y vincular código escrito en ensamblado y traducido por MASM (ml64.exe). Solo tienes que saltar unos aros para habilitarlo:
- Haga clic con el botón derecho en el proyecto en la ventana del Explorador de soluciones, Personalizaciones de compilación, marque "masm".
- Proyecto + Agregar nuevo elemento, elija la plantilla del archivo C ++ pero nómbrelo something.asm
- Asegúrese de tener el objetivo de la plataforma x64 para el proyecto. Build + Configuration Manager, seleccione "x64" en el combo "Active solution platform". Si falta, seleccione
<New>
y elija x64 del primer combo. Si falta, tendrá que volver a ejecutar la instalación y agregar soporte para compiladores de 64 bits.
Escriba el código de ensamblaje usando la sintaxis de MASM, la referencia está aquí . El tutorial de inicio rápido está aquí .
El esqueleto para el código de ensamblado se ve así:
.CODE
PUBLIC Foo
Foo PROC
ret ; TODO: make useful
Foo ENDP
END
Y llamado desde código C ++ como este:
extern "C" void Foo();
int main(int argc, char* argv[])
{
Foo();
return 0;
}
El soporte de depuración completa está disponible, por lo general querrá al menos usar la ventana Depuración + Registros de Windows +.
Implementé un número entero de 256 bits usando una matriz de ensamblaje x64 unsigned long long
usar y usado para implementar el complemento con acarreo. Aquí está la persona que llama C ++:
#include "stdafx.h"
extern "C" void add256(unsigned long long *a, unsigned long long * b, unsigned long long *c);
int _tmain(int argc, _TCHAR* argv[])
{
unsigned long long a[4] = {0x8000000000000001, 2, 3, 4};
unsigned long long b[4] = {0x8000000000000005, 6, 7, 8};
unsigned long long c[4] = {0, 0, 0, 0};
add256(a, b, c); // c[] == {6, 9, 10, 12};
return 0;
}
El add256
se implementa en el ensamblaje:
; void add256(unsigned long long *a, unsigned long long * b, unsigned long long *c)
.CODE
PUBLIC add256
add256 PROC
mov qword ptr [rsp+18h],r8
mov qword ptr [rsp+10h],rdx
mov qword ptr [rsp+8],rcx
push rdi
; c[0] = a[0] + b[0];
mov rax,qword ptr 16[rsp]
mov rax,qword ptr [rax]
mov rcx,qword ptr 24[rsp]
add rax,qword ptr [rcx]
mov rcx,qword ptr 32[rsp]
mov qword ptr [rcx],rax
; c[1] = a[1] + b[1] + CARRY;
mov rax,qword ptr 16[rsp]
mov rax,qword ptr [rax+8]
mov rcx,qword ptr 24[rsp]
adc rax,qword ptr [rcx+8]
mov rcx,qword ptr 32[rsp]
mov qword ptr [rcx+8],rax
; c[2] = a[2] + b[2] + CARRY;
mov rax,qword ptr 16[rsp]
mov rax,qword ptr [rax+10h]
mov rcx,qword ptr 24[rsp]
adc rax,qword ptr [rcx+10h]
mov rcx,qword ptr 32[rsp]
mov qword ptr [rcx+10h],rax
; c[3] = a[3] + b[3] + CARRY;
mov rax,qword ptr 16[rsp]
mov rax,qword ptr [rax+18h]
mov rcx,qword ptr 24[rsp]
adc rax,qword ptr [rcx+18h]
mov rcx,qword ptr 32[rsp]
mov qword ptr [rcx+18h],rax
; }
pop rdi
ret
add256 endp
end
Sé que está indicando que no quería un emulado de agregar con la solución de transporte, y quería una solución de alto rendimiento, pero, aún así, puede considerar la siguiente solución de C ++ que tiene una buena forma de simular números de 256 bits:
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
unsigned long long a[4] = {0x8000000000000001, 2, 3, 4};
unsigned long long b[4] = {0x8000000000000005, 6, 7, 8};
unsigned long long c[4] = {0, 0, 0, 0};
c[0] = a[0] + b[0]; // 6
c[1] = a[1] + b[1] + (c[0] < a[0]); // 9
c[2] = a[2] + b[2] + (c[1] < a[1]); // 10
c[3] = a[3] + b[3] + (c[2] < a[2]); // 12
return 0;
}
Ahora hay un intrínseco para ADC
en _addcarry_u64
: _addcarry_u64
. El siguiente código
#include <inttypes.h>
#include <intrin.h>
#include <stdio.h>
typedef struct {
uint64_t x1;
uint64_t x2;
uint64_t x3;
uint64_t x4;
} uint256;
void add256(uint256 *x, uint256 *y) {
unsigned char c = 0;
c = _addcarry_u64(c, x->x1, y->x1, &x->x1);
c = _addcarry_u64(c, x->x2, y->x2, &x->x2);
c = _addcarry_u64(c, x->x3, y->x3, &x->x3);
_addcarry_u64(c, x->x4, y->x4, &x->x4);
}
int main() {
//uint64_t x1, x2, x3, x4;
//uint64_t y1, y2, y3, y4;
uint256 x, y;
x.x1 = x.x2 = x.x3 = -1; x.x4 = 0;
y.x1 = 2; y.x2 = y.x3 = y.x4 = 0;
printf(" %016" PRIx64 "%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "/n", x.x4, x.x3, x.x2, x.x1);
printf("+");
printf("%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "/n", y.x4, y.x3, y.x2, y.x1);
add256(&x, &y);
printf("=");
printf("%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "/n", x.x4, x.x3, x.x2, x.x1);
}
produce la siguiente salida de ensamblaje de Visual Studio Express 2013
mov rdx, QWORD PTR x$[rsp]
mov r8, QWORD PTR x$[rsp+8]
mov r9, QWORD PTR x$[rsp+16]
mov rax, QWORD PTR x$[rsp+24]
add rdx, QWORD PTR y$[rsp]
adc r8, QWORD PTR y$[rsp+8]
adc r9, QWORD PTR y$[rsp+16]
adc rax, QWORD PTR y$[rsp+24]
que tiene un add
y tres adc
como se esperaba.
Editar:
Parece haber cierta confusión sobre lo que hace _addcarry_u64
. Si mira la documentación de Microsoft para esto a la que me he vinculado al inicio de esta respuesta, muestra que no requiere ningún hardware especial. Esto produce adc
y funcionará en todos los procesadores x86-64 (y _addcarry_u32
funcionaría en procesadores aún más antiguos). Funciona bien en el sistema Ivy Bridge en el que lo probé.
Sin embargo, _addcarryx_u64
requiere adx
(como se muestra en la documentación de MSFT) y, de hecho, no se ejecuta en mi Ivy Bridge System.