Бит переноса, ограничение GAS

Я пишу ассемблерное длинное дополнение во встроенной сборке GAS,

template <std::size_t NumBits>
void inline KA_add(vli<NumBits> & x, vli<NumBits> const& y);

Если бы я специализировался, я мог бы:

template <>
void inline KA_add<128>(vli<128> & x, vli<128> const& y){
     asm("addq  %2, %0; adcq  %3, %1;" :"+r"(x[0]),"+r"(x[1]):"g"(y[0]),"g"(y[1]):"cc");
}

Хорошо, это работает, теперь, если я попытаюсь обобщить, чтобы разрешить встроенный шаблон, и пусть мой компилятор работает для любой длины...

template <std::size_t NumBits>
void inline KA_add(vli<NumBits> & x, vli<NumBits> const& y){
    asm("addq  %1, %0;" :"+r"(x[0]):"g"(y[0]):"cc");
    for(int i(1); i < vli<NumBits>::numwords;++i)
        asm("adcq  %1, %0;" :"+r"(x[i]):"g"(y[i]):"cc");
};

Что ж, это не работает. У меня нет гарантии, что бит переноса (CB) распространяется. Это не сохраняется между первой строкой asm и второй. Это может быть логично, потому что цикл увеличивает i и, таким образом, «удаляет» CB I, должно существовать ограничение GAS для сохранения CB по двум вызовам ASM. К сожалению, я не нахожу такой информации.

Есть идеи ?

Спасибо, Мерси!

PS Я переписываю свою функцию, чтобы убрать идеологию C++

template <std::size_t NumBits>
inline void KA_add_test(boost::uint64_t* x, boost::uint64_t const* y){
    asm ("addq  %1, %0;" :"+r"(x[0]):"g"(y[0]):"cc");
        for(int i(1); i < vli<NumBits>::numwords;++i)
            asm ("adcq  %1, %0;" :"+r"(x[i]):"g"(y[i]):"cc");
};

Ассемблер дает (режим отладки GCC),

ПРИЛОЖЕНИЕ

    addq  %rdx, %rax; 

NO_APP

    movq    -24(%rbp), %rdx
    movq    %rax, (%rdx)

.LBB94: .loc 9 55 0

    movl    $1, -4(%rbp)
    jmp     .L323 

.L324:

    .loc 9 56 0

    movl    -4(%rbp), %eax
    cltq  
    salq    $3, %rax
    movq    %rax, %rdx
    addq    -24(%rbp), %rdx <----------------- Break the carry bit
    movl    -4(%rbp), %eax
    cltq  
    salq    $3, %rax
    addq    -32(%rbp), %rax
    movq    (%rax), %rcx
    movq    (%rdx), %rax

ПРИЛОЖЕНИЕ

    adcq  %rcx, %rax; 

NO_APP

Как мы можем прочитать есть дополнительный addq, он уничтожает распространение CB


person Timocafé    schedule 30.11.2012    source источник
comment
Вероятно, было бы полезно, если бы вы показали нам скомпилированный ассемблерный вывод вашей функции KA_add, потому что, вероятно, что-то между вашими ассемблерными строками изменяет бит переноса или что-то в этом роде.   -  person Tony The Lion    schedule 30.11.2012
comment
Будет ли зацикливание в коде ассемблера приемлемым или вы действительно продолжаете развертывание (если вы согласны с развертыванием только до фиксированного числа итераций, возможно, [библиотека препроцессора ускорения] (www.boost.org/libs/preprocessor/) могла бы использоваться)?   -  person user786653    schedule 03.12.2012
comment
Установлен ли бит флага переноса (CF), когда перенос необходим для вашего addq ?   -  person Tony The Lion    schedule 03.12.2012
comment
В настоящее время я решил pb с помощью решения boost_pp, оно работает, но его трудно читать/отлаживать/обслуживать как примечание (user786653). Нет, Тони, это дополнение сбрасывает на 0 CB, полученный первым встроенным ASM. Так что мой adcq становится бесполезным, очень жаль -_-   -  person Timocafé    schedule 03.12.2012


Ответы (1)


Я не вижу способа явно указать компилятору, что код цикла должен создаваться без инструкций, влияющих на флаг C.

Это, безусловно, возможно сделать - используйте lea для подсчета адресов массива вверх, dec для подсчета цикла вниз и проверьте Z для конечного условия. Таким образом, ничто в цикле, кроме фактической суммы массива, не изменяет флаг C.

Вам нужно будет сделать ручную вещь, например:

long long tmp; // hold a register

__asm__("0:
    movq (%1), %0
    lea 8(%1), %1
    adcq  %0, (%2)
    lea 8(%2), %2
    dec %3
    jnz 0b"
    : "=r"(tmp)
    : "m"(&x[0]), "m"(&y[0]), "r"(vli<NumBits>::numwords)
    : "cc", "memory");

Однако для горячего кода узкая петля не оптимальна; во-первых, у инструкций есть зависимости, и на итерацию приходится значительно больше инструкций, чем встроенных/развернутых adc последовательностей. Лучшей последовательностью будет что-то вроде (%rbp соответственно %rsi с начальными адресами для исходного и целевого массивов):

0:

lea  64(%rbp), %r13
lea  64(%rsi), %r14
movq   (%rbp), %rax
movq  8(%rbp), %rdx
adcq   (%rsi), %rax
movq 16(%rbp), %rcx
adcq  8(%rsi), %rdx
movq 24(%rbp), %r8
adcq 16(%rsi), %rcx
movq 32(%rbp), %r9
adcq 24(%rsi), %r8
movq 40(%rbp), %r10
adcq 32(%rsi), %r9
movq 48(%rbp), %r11
adcq 40(%rsi), %r10
movq 56(%rbp), %r12
adcq 48(%rsi), %r10
movq %rax,   (%rsi)
adcq 56(%rsi), %r10
movq %rdx,  8(%rsi)
movq %rcx, 16(%rsi)
movq %r8,  24(%rsi)
movq %r13, %rbp     // next src
movq %r9,  32(%rsi)
movq %r10, 40(%rsi)
movq %r11, 48(%rsi)
movq %r12, 56(%rsi)
movq %r14, %rsi     // next tgt
dec  %edi           // use counter % 8 (doing 8 words / iteration)
jnz 0b              // loop again if not yet zero 

и зацикливание только вокруг таких блоков. Преимущество будет заключаться в том, что нагрузки будут заблокированы, и вы будете иметь дело с количеством циклов/условием завершения только один раз за это.

Честно говоря, я бы постарался не делать общую разрядность особенно "аккуратной", а предпочел бы использовать специально развернутый код, скажем, для разрядности, равной степеням двойки. Скорее добавьте сообщение флага/конструктора к неоптимизированному экземпляру шаблона, говорящее пользователю «использовать степень двойки»?

person FrankH.    schedule 07.12.2012
comment
Не проще ли просто сохранить регистр флагов в начале функции и восстановить его перед возвратом? PUSHF/POPF и SAHF/LAHF могут сделать именно это. - person Daniel Kamil Kozar; 07.12.2012
comment
Верно, но любой из них означает больший доступ к памяти; делается один раз за плотную итерацию, это дорого. Используя lea / inc / dec этого можно избежать. - person FrankH.; 08.12.2012