Правильный способ обернуть CMPXCHG8B во встроенную сборку GCC, 32 бита - PullRequest
9 голосов
/ 20 июля 2011

Я пытаюсь написать GCC inline asm для CMPXCHG8B для ia32.Нет, я не могу использовать __sync_bool_compare_and_swap.Он должен работать с -fPIC и без него.

Пока что лучшее, что у меня есть ( EDIT : в конце концов, не работает, подробности см. В моем собственном ответе ниже):

register int32 ebx_val asm("ebx")= set & 0xFFFFFFFF;
asm ("lock; cmpxchg8b %0;"
     "setz %1;"
     : "+m" (*a), "=q" (ret), "+A" (*cmp)
     : "r" (ebx_val), "c" ((int32)(set >> 32))
     : "flags")

Однако я не уверен, что это действительно правильно.

Я не могу сделать "b" ((int32)(set & 0xFFFFFFFF)) для ebx_val из-за PIC, но, очевидно, переменная register asm("ebx") принята компилятором.

БОНУС : переменная ret используется для ветвления, поэтому код в конечном итоге выглядит следующим образом:

cmpxchg8b [edi];
setz cl;
cmp cl, 0;
je foo;

Любая идея, как описать выходные операнды, чтобы они стали:

cmpxchg8b [edi]
jz foo

?

Спасибо.

Ответы [ 3 ]

2 голосов
/ 21 июля 2011

Как насчет следующего, который, кажется, работает для меня в небольшом тесте:

int sbcas(uint64_t* ptr, uint64_t oldval, uint64_t newval)
{
    int changed = 0;
    __asm__ (
        "push %%ebx\n\t" // -fPIC uses ebx, so save it
        "mov %5, %%ebx\n\t" // load ebx with needed value
        "lock\n\t"
        "cmpxchg8b %0\n\t" // perform CAS operation
        "setz %%al\n\t" // eax potentially modified anyway
        "movzx %%al, %1\n\t" // store result of comparison in 'changed'
        "pop %%ebx\n\t" // restore ebx
        : "+m" (*ptr), "=r" (changed)
        : "d" ((uint32_t)(oldval >> 32)), "a" ((uint32_t)(oldval & 0xffffffff)), "c" ((uint32_t)(newval >> 32)), "r" ((uint32_t)(newval & 0xffffffff))
        : "flags", "memory"
        );
    return changed;
}

Если это также будет неправильно скомпилировано, не могли бы вы добавить небольшой фрагмент, который вызывает такое поведение?* Что касается вопроса о бонусе, я не думаю, что возможно выполнить ветвление после блока ассемблера, используя код условия из инструкции cmpxchg8b (если вы не используете asm goto или подобную функциональность).Из GNU C Language Extensions :

Естественно искать способ предоставить доступ к коду условия, оставленному инструкцией ассемблера.Однако, когда мы попытались реализовать это, мы не нашли способа заставить его работать надежно.Проблема в том, что выходные операнды могут нуждаться в перезагрузке, что приведет к дополнительным следующим инструкциям «сохранения».На большинстве машин эти инструкции изменяют код условия до того, как появится время для его проверки.Эта проблема не возникает для обычных команд «тест» и «сравнение», потому что у них нет выходных операндов.

РЕДАКТИРОВАТЬ: Я не могу найти источник, который так или иначе указывает, можно ли изменять стек, также используя входные значения %N ( This Древняя ссылка гласит: «Вы даже можете поместить свои регистры в стек, использовать их и поместить обратно», но в примере нет входных данных).

Но можно обойтись без фиксации значений в других регистрах:

int sbcas(uint64_t* ptr, uint64_t oldval, uint64_t newval)
{
    int changed = 0;
    __asm__ (
        "push %%ebx\n\t" // -fPIC uses ebx
        "mov %%edi, %%ebx\n\t" // load ebx with needed value
        "lock\n\t"
        "cmpxchg8b (%%esi)\n\t"
        "setz %%al\n\t" // eax potentially modified anyway
        "movzx %%al, %1\n\t"
        "pop %%ebx\n\t"
        : "+S" (ptr), "=a" (changed)
        : "0" (ptr), "d" ((uint32_t)(oldval >> 32)), "a" ((uint32_t)(oldval & 0xffffffff)), "c" ((uint32_t)(newval >> 32)), "D" ((uint32_t)(newval & 0xffffffff))
        : "flags", "memory"
        );
    return changed;
}
2 голосов
/ 20 июля 2011

Вот что у меня есть:

bool
spin_lock(int64_t* lock, int64_t thread_id, int tries)
{
    register int32_t pic_hack asm("ebx") = thread_id & 0xffffffff;
retry:
    if (tries-- > 0) {
        asm goto ("lock cmpxchg8b %0; jnz %l[retry]"
                  :
                  : "m" (*lock), "A" ((int64_t) 0),
                    "c" ((int32_t) (thread_id >> 32)), "r" (pic_hack)
                  :
                  : retry);
        return true;
    }
    return false;
}

Он использует функцию asm goto, новую для gcc 4.5, которая позволяет переходить из встроенной сборки в метки C. (О, я вижу ваш комментарий о необходимости поддержки старых версий gcc. О, хорошо. Я пытался. :-P)

1 голос
/ 21 июля 2011

Удивительно, но фрагмент кода в вопросе все еще неправильно компилируется в некоторых обстоятельствах: если операнд с нулевым значением asm косвенно адресуется через EBX (PIC) до того, как регистр EBX установлен с register asm, то gcc переходит к загрузить операнд через EBX после того, как ему присвоено значение set & 0xFFFFFFFF!

Это код, который я сейчас пытаюсь заставить работать: (РЕДАКТИРОВАТЬ: избегать push / pop)

asm ("movl %%edi, -4(%%esp);"
     "leal %0, %%edi;" 
     "xchgl %%ebx, %%esi;"
     "lock; cmpxchg8b (%%edi);" // Sets ZF
     "movl %%esi, %%ebx;"       // Preserves ZF
     "movl -4(%%esp), %%edi;"   // Preserves ZF
     "setz %1;"                 // Reads ZF
     : "+m" (*a), "=q" (ret), "+A" (*cmp)
     : "S" ((int32)(set & 0xFFFFFFFF)), "c" ((int32)(set >> 32))
     : "flags")

Идея здесь состоит в том, чтобы загрузить операнды перед блокировкой EBX, а также избежать любой косвенной адресации при установке значения EBX для CMPXCHG8B. Я фиксирую жесткий регистр ESI для нижней половины операнда, потому что если бы я этого не сделал, GCC мог бы свободно использовать любой другой уже принятый регистр, если бы он мог доказать, что значение было равно. Регистр EDI сохраняется вручную, так как простое добавление его в закрытый список регистров дросселей GCC с «невозможными перезагрузками», вероятно, из-за высокого давления в регистре. PUSH / POP избегается при сохранении EDI, поскольку другие операнды могут быть адресованы ESP.

...