adición de varias palabras usando la bandera de acarreo

GCC tiene enteros de 128 bits. Usando estos puedo hacer que el compilador use elmul (oimul con un solo operando) instrucciones. Por ejemplo

uint64_t x,y;
unsigned __in128 z = (unsigned __int128)x*y;

producemul. He usado esto para crear una función de 128x128 a 256 (consulte el final de esta pregunta, antes de la actualización, para obtener un código para eso si está interesado).

Ahora quiero hacer una adición de 256 bits y no he encontrado una manera de usar el compiladorADC excepto mediante el uso de ensamblaje. Podría usar un ensamblador pero quiero funciones en línea para mayor eficiencia. El compilador ya produce una función eficiente de 128x128 a 256 (por la razón que expliqué al comienzo de esta pregunta), así que no veo por qué debería reescribir esto también en el ensamblaje (o cualquier otra función que el compilador ya implemente eficientemente) .

Aquí está la función de ensamblaje en línea que se me ocurrió:

#define ADD256(X1, X2, X3, X4, Y1, Y2, Y3, Y4) \
 __asm__ __volatile__ ( \
 "addq %[v1], %[u1] \n" \
 "adcq %[v2], %[u2] \n" \
 "adcq %[v3], %[u3] \n" \
 "adcq %[v4], %[u4] \n" \
 : [u1] "+&r" (X1), [u2] "+&r" (X2), [u3] "+&r" (X3), [u4] "+&r" (X4) \
 : [v1]  "r" (Y1), [v2]  "r" (Y2), [v3]  "r" (Y3), [v4]  "r" (Y4)) 

(probablemente no todas las salidas necesitan unmodificador de clobber temprano pero obtengo el resultado incorrecto sin al menos los dos últimos)

Y aquí hay una función que hace lo mismo en C

void add256(int256 *x, int256 *y) {
    uint64_t t1, t2;
    t1 = x->x1; x->x1 += y->x1;
    t2 = x->x2; x->x2 += y->x2 + ((x->x1) < t1);
    t1 = x->x3; x->x3 += y->x3 + ((x->x2) < t2);
                x->x4 += y->x4 + ((x->x3) < t1);
}

¿Por qué es necesario el montaje para esto? ¿Por qué el compilador no puede compilar eladd256 función para usar las banderas de transporte? ¿Hay alguna manera de obligar al compilador a hacer esto (por ejemplo, puedo cambiaradd256 para que haga esto)? ¿Qué se supone que debe hacer alguien porun compilador que no admite ensamblaje en línea (escriba todas las funciones en el ensamblado?) ¿Por qué no hay nada intrínseco para esto?

Aquí está la función de 128x128 a 256

void muldwu128(int256 *w, uint128 u, uint128 v) {
   uint128 t;
   uint64_t u0, u1, v0, v1, k, w1, w2, w3;

   u0 = u >> 64L;
   u1 = u;
   v0 = v >> 64L;
   v1 = v;

   t = (uint128)u1*v1;
   w3 = t;
   k = t >> 64L;

   t = (uint128)u0*v1 + k;
   w2 = t;
   w1 = t >> 64L;
   t = (uint128)u1*v0 + w2;
   k = t >> 64L;

   w->hi = (uint128)u0*v0 + w1 + k;
   w->lo = (t << 64L) + w3;

}

Algún tipo define:

typedef          __int128  int128;
typedef unsigned __int128 uint128;

typedef union {
    struct {
        uint64_t x1;
        uint64_t x2;
         int64_t x3;
         int64_t x4;
    };
    struct {
        uint128 lo;
         int128 hi;
    };
} int256;

Actualizar:

Mi pregunta es en gran parte un duplicado de estas preguntas:

get-gcc-to-use-carry-logic-for-arbitrary-precision-arithmetic-without-inline-assemblyeficiente-128-bit-adicion-using-carry-flagadición de varias palabras en c.

Intel tiene un buen artículo (Nuevas instrucciones soportan aritmética de enteros grandes) que analiza la aritmética de enteros grandes y las tres nuevas instrucciones MULX, ADCX, ADOX. Escriben:

Las definiciones intrínsecas de mulx, adcx y adox también se integrarán en los compiladores. Este es el primer ejemplo de una instrucción de tipo "agregar con carry" que se implementa con intrínsecos. El soporte intrínseco permitirá a los usuarios implementar aritmética de enteros grandes utilizando lenguajes de programación de nivel superior como C / C ++.

Los intrínsecos son

unsigned __int64 umul128(unsigned __int64 a, unsigned __int64 b, unsigned __int64 * hi);
unsigned char _addcarry_u64(unsigned char c_in, unsigned __int64 a, unsigned __int64 b, unsigned __int64 *out);
unsigned char _addcarryx_u64(unsigned char c_in, unsigned __int64 a, unsigned __int64 b, unsigned __int64 *out);

Por cierto, MSVC ya tiene un_umul128 intrínseco. Entonces, aunque MSVC no tiene__int128 el_umul128 intrínseco se puede utilizar para generarmul y por lo tanto, multiplicación de 128 bits.

losMULX Instruciton está disponible desde BMI2 en Haswell. losADCX yADOX Las instrucciones están disponibles para los procesadores Broadwell. Es una pena que no haya intrínseco paraADC que ha estado disponible desde el 8086 en 1979. Eso resolvería el problema del ensamblaje en línea.

Editar: en realidad__int128 utilizarámulx si se define BMI2 (por ejemplo, usando-mbmi2 o -march=haswell)

Editar:

Probé el complemento de Clang con carry builtins como lo sugirió Lưu Vĩnh Phúc

void add256(int256 *x, int256 *y) {
    unsigned long long carryin=0, carryout;
    x->x1 = __builtin_addcll(x->x1, y->x1, carryin, &carryout); carryin = carryout;
    x->x2 = __builtin_addcll(x->x2, y->x2, carryin, &carryout); carryin = carryout;
    x->x3 = __builtin_addcll(x->x3, y->x3, carryin, &carryout); carryin = carryout;
    x->x4 = __builtin_addcll(x->x4, y->x4, carryin, &carryout);  
}

pero esto no generaADC Y es más complicado de lo que espero.

Respuestas a la pregunta(1)

Su respuesta a la pregunta