Как получить доступ к C struct/variables из встроенного asm?

Рассмотрим следующий код:

    int bn_div(bn_t *bn1, bn_t *bn2, bn_t *bnr)
  {
    uint32 q, m;        /* Division Result */
    uint32 i;           /* Loop Counter */
    uint32 j;           /* Loop Counter */

    /* Check Input */
    if (bn1 == NULL) return(EFAULT);
    if (bn1->dat == NULL) return(EFAULT);
    if (bn2 == NULL) return(EFAULT);
    if (bn2->dat == NULL) return(EFAULT);
    if (bnr == NULL) return(EFAULT);
    if (bnr->dat == NULL) return(EFAULT);


    #if defined(__i386__) || defined(__amd64__)
    __asm__ (".intel_syntax noprefix");
    __asm__ ("pushl %eax");
    __asm__ ("pushl %edx");
    __asm__ ("pushf");
    __asm__ ("movl %eax, (bn1->dat[i])");
    __asm__ ("xorl %edx, %edx");
    __asm__ ("divl (bn2->dat[j])");
    __asm__ ("movl (q), %eax");
    __asm__ ("movl (m), %edx");
    __asm__ ("popf");
    __asm__ ("popl %edx");
    __asm__ ("popl %eax");
    #else
    q = bn->dat[i] / bn->dat[j];
    m = bn->dat[i] % bn->dat[j];
    #endif
    /* Return */
    return(0);
  }

Типы данных uint32 - это, в основном, unsigned long int или 32-битное целое число без знака uint32_t. Тип bnint - это либо unsigned short int (uint16_t), либо uint32_t в зависимости от того, доступны ли 64-битные типы данных или нет. Если доступно 64-битное значение, тогда bnint является uint32, иначе это uint16. Это было сделано для захвата переноса/переполнения в других частях кода. Структура bn_t определяется следующим образом:

typedef struct bn_data_t bn_t;
struct bn_data_t
  {
    uint32 sz1;         /* Bit Size */
    uint32 sz8;         /* Byte Size */
    uint32 szw;         /* Word Count */
    bnint *dat;         /* Data Array */
    uint32 flags;       /* Operational Flags */
  };

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

system:/home/user/c/m3/bn 1036 $$$ ->make
clang -I. -I/home/user/c/m3/bn/.. -I/home/user/c/m3/bn/../include  -std=c99 -pedantic -Wall -Wextra -Wshadow -Wpointer-arith -Wcast-align -Wstrict-prototypes  -Wmissing-prototypes -Wnested-externs -Wwrite-strings -Wfloat-equal  -Winline -Wunknown-pragmas -Wundef -Wendif-labels  -c /home/user/c/m3/bn/bn.c
/home/user/c/m3/bn/bn.c:302:12: warning: unused variable 'q' [-Wunused-variable]
    uint32 q, m;        /* Division Result */
           ^
/home/user/c/m3/bn/bn.c:302:15: warning: unused variable 'm' [-Wunused-variable]
    uint32 q, m;        /* Division Result */
              ^
/home/user/c/m3/bn/bn.c:303:12: warning: unused variable 'i' [-Wunused-variable]
    uint32 i;           /* Loop Counter */
           ^
/home/user/c/m3/bn/bn.c:304:12: warning: unused variable 'j' [-Wunused-variable]
    uint32 j;           /* Loop Counter */
           ^
/home/user/c/m3/bn/bn.c:320:14: error: unknown token in expression
    __asm__ ("movl %eax, (bn1->dat[i])");
             ^
<inline asm>:1:18: note: instantiated into assembly here
        movl %eax, (bn1->dat[i])
                        ^
/home/user/c/m3/bn/bn.c:322:14: error: unknown token in expression
    __asm__ ("divl (bn2->dat[j])");
             ^
<inline asm>:1:12: note: instantiated into assembly here
        divl (bn2->dat[j])
                  ^
4 warnings and 2 errors generated.
*** [bn.o] Error code 1

Stop in /home/user/c/m3/bn.
system:/home/user/c/m3/bn 1037 $$$ ->

Что я знаю:

Я считаю себя довольно хорошо разбирающимся в ассемблере x86 (о чем свидетельствует код, который я написал выше). Однако в последний раз, когда я смешивал язык высокого уровня, а ассемблер использовал Borland Pascal около 15-20 лет назад при написании графических драйверов для игр (до Windows 95). Мое знакомство с синтаксисом Intel.

Что я не знаю:

Как мне получить доступ к элементам bn_t (особенно * dat) из asm? Поскольку * dat является указателем на uint32, я обращаюсь к элементам как к массиву (например, bn1- > dat [i]).

Как получить доступ к локальным переменным, объявленным в стеке?

Я использую push/pop для восстановления скребковых регистров до их предыдущих значений, чтобы не нарушать компилятор. Однако мне также нужно включить ключевое слово volatile в локальные переменные?

Или, есть ли лучший способ, о котором я не знаю? Я не хочу помещать это в отдельный вызов функции из-за накладных расходов, поскольку эта функция критична по производительности.

Дополнительно:

Прямо сейчас, я только начинаю писать эту функцию, чтобы она не была полной. Отсутствуют петли и другой такой код поддержки/клея. Но основной смысл заключается в доступе к локальным переменным/элементам структуры.

ИЗМЕНИТЬ 1:

Синтаксис, который я использую, кажется единственным, который поддерживает clang. Я попробовал следующий код, и clang дал мне всевозможные ошибки:

__asm__ ("pushl %%eax",
    "pushl %%edx",
    "pushf",
    "movl (bn1->dat[i]), %%eax",
    "xorl %%edx, %%edx",
    "divl ($0x0c + bn2 + j)",
    "movl %%eax, (q)",
    "movl %%edx, (m)",
    "popf",
    "popl %%edx",
    "popl %%eax"
    );

Он хочет, чтобы я поставил закрывающую скобку в первой строке, заменив запятую. Я перешел на использование %% вместо%, потому что я где-то читал, что встроенная сборка требует, чтобы %% обозначала регистры CPU, а clang рассказывал мне, что я использовал недопустимую escape-последовательность.

Ответы

Ответ 1

Если вам нужно только 32b/32b = > 32-битное деление, , пусть компилятор использует оба вывода div, которые gcc, clang и icc все делают нормально, как вы можете видеть на Исследователь компилятора Godbolt:

uint32_t q = bn1->dat[i] / bn2->dat[j];
uint32_t m = bn1->dat[i] % bn2->dat[j];

Компиляторы довольно хороши в CSE, что в одном div. Просто убедитесь, что вы не сохраняете результат разделения где-то, что gcc не может доказать, не повлияет на ввод остатка.

например. *m = dat[i] / dat[j] может перекрываться (псевдоним) dat[i] или dat[j], поэтому gcc должен перезагрузить операнды и повторить div для операции %. См. Ссылку godbolt для плохих/хороших примеров.


Использование inline asm для 32bit/32bit = 32bit div не дает вам ничего, и на самом деле делает худший код с clang (см. ссылку godbolt).

Если вам нужно 64bit/32bit = 32bit, вам, вероятно, понадобится asm, хотя, если для него нет встроенного компилятора. (GNU C не имеет одного, AFAICT). Очевидный способ в C (литье операндов в uint64_t) генерирует вызов 64-битной/64-битной = 64-битной функции libgcc, которая имеет ветки и несколько инструкций div. gcc не очень хорошо доказывает, что результат будет соответствовать 32 бит, поэтому одна инструкция div не вызывает #DE.

Для многих других инструкций вы можете избежать многократного написания inline asm с помощью встроенных функций для таких вещей, как popcount. С помощью -mpopcnt он компилируется в команду popcnt (и учитывает ложную зависимость от выходного операнда, который имеют процессоры Intel). Без этого он компилируется в вызов функции libgcc.

Всегда предпочитайте встроенные функции или чистый C, который компилируется в хороший asm, поэтому компилятор знает, что делает код. Когда inlining делает некоторые из аргументов, известных во время компиляции, чистый C может быть оптимизирован или упрощен, но код с использованием inline asm будет просто загружаться константы в регистры и выполнить div во время выполнения. Inline asm также побеждает CSE между аналогичными вычислениями на одни и те же данные и, конечно, не может авто-векторизовать.


Использование синтаксиса GNU C правильным способом

https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html объясняет, как рассказать ассемблеру, какие переменные вы хотите в регистрах, и какие выходы.

Вы можете использовать синтаксис и мнемонику с поддержкой Intel/MASM, а также имена имен% n, если хотите, желательно, компилируя с помощью -masm=intel. Синтаксическая ошибка AT & T ( fsub и fsubr мнемоники отменены) все еще может присутствовать в режиме синтаксиса; Я забыл.

Большинство проектов программного обеспечения, которые используют встроенный asm GNU C, используют только синтаксис AT & T.

См. также нижнюю часть этого ответа для получения дополнительной информации об asm-информации GNU C и теги wiki.


Оператор an asm принимает одну строку arg и 3 набора ограничений. Самый простой способ сделать это многострочным: сделать каждую строку asm отдельной строкой, заканчивающейся с помощью \n, и позволить компилятору неявно их конкатенировать.

Кроме того, вы сообщаете компилятору, который регистрирует, что вы хотите добавить материал. Тогда, если переменные уже находятся в регистре, компилятор не должен их разливать, а вы загружаете и сохраняете их. Это действительно застрелится в ноге. учебник Бретт Хейл, связанный в комментариях, надеюсь, охватывает все это.


Правильный пример div с GNU C inline asm

Вы можете увидеть выход asm компилятора для этого на godbolt.

uint32_t q, m;  // this is unsigned int on every compiler that supports x86 inline asm with this syntax, but not when writing portable code.

asm ("divl %[bn2dat_j]\n"
      : "=a" (q), "=d" (m) // results are in eax, edx registers
      : "d" (0),           // zero edx for us, please
        "a" (bn1->dat[i]), // "a" means EAX / RAX
        [bn2dat_j] "mr" (bn2->dat[j]) // register or memory, compiler chooses which is more efficient
      : // no register clobbers, and we don't read/write "memory" other than operands
    );

"divl %4" тоже работал бы, но именованные входы/выходы не меняют имя при добавлении дополнительных ограничений ввода/вывода.