Maneira correta de agrupar o CMPXCHG8B no assembly in-line do GCC, 32 bits

9

Estou tentando escrever o GCC inline asm para o CMPXCHG8B para o ia32. Não, não posso usar __sync_bool_compare_and_swap . Tem que trabalhar com e sem -fPIC.

Até agora, o melhor que eu tenho ( EDIT : não funciona, afinal, veja minha própria resposta abaixo para detalhes) é

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")

No entanto, não tenho certeza se isso está correto.

Não consigo fazer "b" ((int32)(set & 0xFFFFFFFF)) para ebx_val devido ao PIC, mas aparentemente register asm("ebx") variable é aceito pelo compilador.

BONUS : a variável ret é usada para ramificação, então o código fica assim:

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

Qualquer ideia de como descrever os operandos de saída para que ele se torne:

cmpxchg8b [edi]
jz foo

?

Obrigado.

    
por Laurynas Biveinis 20.07.2011 в 06:23
fonte

3 respostas

2

Como sobre o seguinte, que parece funcionar para mim em um pequeno teste:

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;
}

Se isso também for malcompreendido, por favor inclua um pequeno trecho que desencadeie esse comportamento?

Com relação à questão do bônus, não acho que seja possível ramificar após o bloco assembler usando o código de condição da instrução cmpxchg8b (a menos que você use o asm goto ou funcionalidade semelhante). De extensões de linguagem GNU C :

  

É uma ideia natural procurar uma maneira de fornecer acesso ao código de condição deixado pela instrução assembler. No entanto, quando tentamos implementar isso, não encontramos maneira de fazê-lo funcionar de maneira confiável. O problema é que os operandos de saída podem precisar de recarga, o que resultaria em instruções adicionais de "armazenamento". Na maioria das máquinas, essas instruções alterariam o código de condição antes que houvesse tempo para testá-lo. Esse problema não surge para as instruções comuns de "teste" e "comparação" porque elas não possuem nenhum operando de saída.

EDIT: Não consigo encontrar nenhuma fonte que especifique de uma forma ou de outra se não há problema em modificar a pilha e, ao mesmo tempo, usar os valores da entrada %N ( Este antigo link diz" Você pode até mesmo empurrar seus registradores para a pilha, usá-los e colocá-los de volta. ", mas o exemplo não tem entrada) .

Mas deve ser possível fazer isso sem fixar os valores para outros registradores:

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;
}
    
por user786653 21.07.2011 / 14:18
fonte
2

Isso é o que eu tenho:

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;
}

Ele usa o recurso asm goto , novo com o gcc 4.5, que permite saltos de montagem inline para rótulos C. (Ah, eu vejo o seu comentário sobre ter que suportar versões antigas do gcc. Ah, bem. Eu tentei. :-P)

    
por Chris Jester-Young 20.07.2011 / 07:25
fonte
1

Por incrível que pareça, o fragmento de código na questão ainda é malcomposto em algumas circunstâncias: se o operando zero-asm é indiretamente endereçável através do EBX (PIC) antes do registro EBX ser configurado com register asm , então o gcc prossegue para carrega o operando através do EBX depois de ser atribuído a set & 0xFFFFFFFF !

Este é o código que estou tentando fazer funcionar agora: (EDIT: evite 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")

A idéia aqui é carregar os operandos antes de danificar o EBX, também evitar qualquer endereçamento indireto ao configurar o valor EBX para o CMPXCHG8B. Eu corrijo o ESI de registro difícil para a metade inferior do operando, porque se eu não o fizesse, o GCC se sentiria livre para reutilizar qualquer outro registro já recebido se pudesse provar que o valor era igual. O registrador EDI é salvo manualmente, pois simplesmente adicioná-lo à lista de registradores roubados sufoca o GCC com "recarregamentos impossíveis", provavelmente devido à alta pressão de registro. O PUSH / POP é evitado ao salvar o EDI, já que outros operandos podem ser endereçados por ESP.

    
por Laurynas Biveinis 21.07.2011 / 13:21
fonte