at & t asm встроенная проблема c ++ - PullRequest
1 голос
/ 24 декабря 2009

Мой код

const int howmany = 5046;
char buffer[howmany];
    asm("lea     buffer,%esi"); //Get the address of buffer
    asm("mov     howmany,%ebx");         //Set the loop number
    asm("buf_loop:");                      //Lable for beginning of loop
    asm("movb     (%esi),%al");             //Copy buffer[x] to al
    asm("inc     %esi");                   //Increment buffer address
    asm("dec     %ebx");                   //Decrement loop count
    asm("jnz     buf_loop");              //jump to buf_loop if(ebx>0)

Моя проблема

Я использую компилятор gcc. По некоторым причинам мои переменные буфера / howmany не определены в глазах моего ассма. Я не уверен почему. Я просто хочу переместить начальный адрес моего буферного массива в регистр esi, зациклить его «сколько раз» при копировании каждого элемента в регистр al.

Ответы [ 3 ]

7 голосов
/ 24 декабря 2009

Вы используете встроенный ассемблер в gcc? (Если нет, то в каком другом C ++ компиляторе?)

Если gcc, см. Подробности здесь и, в частности, этот пример:

    asm ("leal (%1,%1,4), %0"
         : "=r" (five_times_x)
         : "r" (x) 
         );

%0 и %1 относятся к переменным уровня C, и они указаны в качестве второго (для выходов) и третьего (для входов) параметров для asm. В вашем примере у вас есть только «входные данные», поэтому у вас будет пустой второй операнд (традиционно каждый использует комментарий после этого двоеточия, например /* no output registers */, чтобы указать это более явно).

1 голос
/ 17 ноября 2017

Все эти инструкции asm должны быть в одинаковом asm выражении, если вы хотите быть уверены, что они непрерывны (без сгенерированного компилятором кода между ними), и вам нужно объявить ввод / output / clobber операнды или вы наступите на регистры компилятора.

Вы не можете использовать lea или mov в / из имени переменной C (за исключением глобальных / статических символов, которые фактически определены в выводе asm компилятора, но даже тогда вы обычно не должны).

Вместо использования mov инструкций для настройки входных данных, попросите компилятор сделать это для вас, используя ограничения входных операндов. Если первая или последняя инструкция встроенного asm-оператора GNU C, обычно это означает, что вы делаете это неправильно и пишете неэффективный код.

И, кстати, GNU C ++ допускает массивы переменной длины в стиле C99, поэтому howmany может быть не const и даже установлен таким образом, что не оптимизируется до константы. Любой компилятор, который может компилировать встроенный asm в стиле GNU, также будет поддерживать массивы переменной длины.


Как правильно написать цикл

Если это выглядит слишком сложно, то https://gcc.gnu.org/wiki/DontUseInlineAsm. Напишите отдельную функцию в asm, чтобы вы могли просто изучить asm вместо того, чтобы изучать gcc и его сложный, но мощный интерфейс inline-asm. В основном вам нужно знать asm и понимать компиляторы, чтобы правильно его использовать (с правильными ограничениями для предотвращения поломок при включенной оптимизации).

Обратите внимание на использование именованных операндов, таких как %[ptr] вместо %2 или %%ebx. Позволить компилятору выбирать, какие регистры использовать, как правило, хорошо, но для x86 есть буквы, отличные от "r", которые вы можете использовать, например, "=a" для rax / eax / ax / al. См. https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html,, а также другие ссылки в вики-теге inline-Assembly .

Я также использовал buf_loop%=: для добавления уникального номера к метке, поэтому, если оптимизатор клонирует функцию или вставляет ее в несколько мест, файл все равно будет собран.

Исходный код + выходные данные компилятора в проводнике компилятора Godbolt .

void ext(char *);

int foo(void) 
{
    int howmany = 5046;   // could be a function arg
    char buffer[howmany];
    //ext(buffer);

    const char *bufptr = buffer;  // copy the pointer to a C var we can use as a read-write operand
    unsigned char result;
    asm("buf_loop%=:  \n\t"                 // do {
        "   movb     (%[ptr]), %%al \n\t"   // Copy buffer[x] to al
        "   inc     %[ptr]        \n\t"
        "   dec     %[count]      \n\t"
        "   jnz     buf_loop      \n\t"      // } while(ebx>0)
       :   [res]"=a"(result)      // al = write-only output
         , [count] "+r" (howmany) // input/output operand, any register
         , [ptr] "+r" (bufptr)
       : // no input-only operands
       : "memory"   // we read memory that isn't an input operand, only pointed to by inputs
    );
    return result;
}

Я использовал %%al в качестве примера того, как явно писать имена регистров: Extended Asm (с операндами) требуется двойной %, чтобы получить литерал % в выводе asm. Вы также можете использовать %[res] или %0 и позволить компилятору подставлять %al в свой вывод asm. (И тогда у вас не будет причин использовать ограничение конкретного регистра, если вы не захотите воспользоваться cbw или lodsb или чем-то подобным.) result - это unsigned char, поэтому компилятор выберет регистр байтов для этого. Если вам нужен младший байт более широкого операнда, вы можете использовать, например, %b[count].

При этом используется "memory" clobber, который неэффективен . Вам не нужно, чтобы компилятор пролил все в память, только для того, чтобы убедиться, что содержимое buffer[] в памяти соответствует состоянию абстрактной машины C. (Это не гарантируется передачей указателя в регистр).

gcc7.2 -O3 вывод:

    pushq   %rbp
    movl    $5046, %edx
    movq    %rsp, %rbp
    subq    $5056, %rsp
    movq    %rsp, %rcx         # compiler-emitted to satisfy our "+r" constraint for bufptr
    # start of the inline-asm block
    buf_loop18:  
       movb     (%rcx), %al 
       inc     %rcx        
       dec     %edx      
       jnz     buf_loop      
    # end of the inline-asm block

    movzbl  %al, %eax
    leave
    ret

Без расширения памяти или ограничения ввода, leave появляется перед встроенным блоком asm, освобождая эту память стека до того, как встроенный asm использует устаревший указатель. Обработчик сигнала, запущенный в неподходящее время, может засорить его.


Более эффективный способ - использовать фиктивный операнд памяти, который сообщает компилятору, что весь массив является входом памяти только для чтения для оператора asm. См. , чтобы получить длину строки в встроенный GNU Assembler , чтобы больше узнать об этом трюке с гибким элементом массива, чтобы сообщить компилятору о том, что вы прочитали весь массив, без явного указания длины.

В C вы можете определить новый тип внутри приведения, но вы не можете в C ++, поэтому using вместо действительно сложного входного операнда.

int bar(unsigned howmany)
{
    //int howmany = 5046;
    char buffer[howmany];
    //ext(buffer);
    buffer[0] = 1;
    buffer[100] = 100;   // test whether we got the input constraints right

    //using input_t = const struct {char a[howmany];};  // requires a constant size
    using flexarray_t = const struct {char a; char x[];};
    const char *dummy;
    unsigned char result;
    asm("buf_loop%=:  \n\t"                 // do {
        "   movb     (%[ptr]), %%al \n\t"   // Copy buffer[x] to al
        "   inc     %[ptr]        \n\t"
        "   dec     %[count]      \n\t"
        "   jnz     buf_loop      \n\t"      // } while(ebx>0)
       : [res]"=a"(result)        // al = write-only output
         , [count] "+r" (howmany) // input/output operand, any register
         , "=r" (dummy)           // output operand in the same register as buffer input, so we can modify the register
       : [ptr] "2" (buffer)     // matching constraint for the dummy output
         , "m" (*(flexarray_t *) buffer)  // whole buffer as an input operand

           //, "m" (*buffer)        // just the first element: doesn't stop the buffer[100]=100 store from sinking past the inline asm, even if you used asm volatile
       : // no clobbers
    );
    buffer[100] = 101;
    return result;
}

Я также использовал ограничение соответствия, чтобы buffer мог быть непосредственно входным, а выходной операнд в том же регистре означает, что мы можем изменить этот регистр. Мы получили тот же эффект в foo(), используя const char *bufptr = buffer; и затем используя ограничение чтения-записи, чтобы сообщить компилятору, что новое значение этой переменной C - это то, что мы оставляем в регистре. В любом случае мы оставляем значение в мертвой переменной C, которая выходит из области видимости без чтения, но способ ограничения соответствия может быть полезен для макросов, где вы не хотите изменять значение ввода (и не нуждаетесь в тип ввода: int dummy тоже будет работать нормально.)

Назначения buffer[100] = 100; и buffer[100] = 101; предназначены для того, чтобы показать, что они оба появляются в asm, а не объединяются в inline-asm (что происходит, если вы пропустите входной операнд "m"). IDK, почему buffer[100] = 101; не оптимизирован; он мертв, так и должно быть. Также обратите внимание, что asm volatile не не блокирует это переупорядочение, поэтому это не альтернатива "memory" clobber или использование правильных ограничений.

1 голос
/ 24 декабря 2009

Часть, которая объявляет такой массив

int howmany = 5046;
char buffer[howmany];

не является допустимым C ++. В C ++ невозможно объявить массив, который имеет «переменную» или размер во время выполнения. В объявлениях массива C ++ размер всегда является константой времени компиляции.

Если ваш компилятор допускает это объявление массива, это означает, что он реализует его как расширение. В этом случае вам нужно провести собственное исследование, чтобы выяснить, как он реализует такой встроенный во время выполнения массив. Я бы предположил, что внутренне buffer будет реализован как указатель , а не как истинный массив. Если мои предположения верны и это действительно указатель, то правильный способ загрузить адрес массива в esi может быть

mov buffer,%esi

а не lea, как в вашем коде. lea будет работать только с "обычными" массивами размера во время компиляции, но не с массивами размера во время выполнения.

Еще один вопрос: нужен ли вам в вашем массиве размер во время выполнения? Может быть, вы просто сделали это по ошибке? Если вы просто измените объявление howmany на

const int howmany = 5046;

массив превратится в "нормальный" массив C ++, и ваш код может начать работать как есть (то есть с lea).

...