Как включает сборку в линию с кодом C?

Я видел код для Arduino и других аппаратных средств, которые имеют встроенную сборку с C, что-то вроде:

asm("movl %ecx %eax"); /* moves the contents of ecx to eax */
__asm__("movb %bh (%eax)"); /*moves the byte from bh to the memory pointed by eax */

Как это работает? Я понимаю, что каждый компилятор отличается, но каковы общие причины этого, и как кто-то может воспользоваться этим?

Ответы

Ответ 1

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

В любом случае, много причин, почему кто-то может захотеть сделать это, но вам действительно нужно знать, что вы делаете. Эти куски кода будут довольно непрозрачными для вашего компилятора, и, скорее всего, вы не получите никаких предупреждений, если вы делаете что-то плохое.

Ответ 2

Обычно компилятор просто вставляет инструкции ассемблера прямо в свой сгенерированный выход ассемблера. И он будет делать это без каких-либо последствий.

Например, в этом коде оптимизатор выполняет размножение копии, в результате чего он видит, что y = x, то z = y. Поэтому он заменяет z = y на z = x, надеясь, что это позволит ему выполнять дальнейшие оптимизации. Как бы то ни было, он не заметил, что я вовремя испортил значение x.

char x=6;
char y,z;

y=x;                 // y becomes 6

_asm                    
    rrncf x, 1       // x becomes 3. Optimiser doesn't see this happen!
_endasm  

z=y;                 // z should become 6, but actually gets
                     // the value of x, which is 3

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

volatile char x=6;   // Tell the compiler that this variable could change
                     // all by itself, and any time, and therefore don't
                     // optimise with it.
char y,z;

y=x;                 // y becomes 6

_asm                    
    rrncf x, 1       // x becomes 3. Optimiser doesn't see this happen!
_endasm  

z=y;                 // z correctly gets the value of y, which is 6

Ответ 3

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

Наиболее часто используемым для ассемблерного кода является использование специализированных инструкций процессора, которые компилятор не может сгенерировать. Например, отключение прерываний для критического раздела, управление функциями процессора (кеш, MMU, MPU, управление питанием, запрос возможностей ЦП,...), доступ к сопроцессорам и периферийным устройствам оборудования (например, инструкции inb/outb на x86) и т.д. Вы редко найдете asm("movl %ecx %eax"), потому что это влияет на регистры общего назначения, которые также использует код C вокруг него, но что-то вроде asm("mcr p15, 0, 0, c7, c10, 5") имеет свое применение (барьер памяти данных на ARM). OSDev wiki содержит несколько примеров с фрагментами кода.

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

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

Существует два способа комбинирования сборки с C: с встроенной сборкой или путем связывания сборочных модулей с модулями C. Связывание, возможно, более чистое, но не всегда применимое: иногда вам нужна эта одна команда в середине функции (например, для сохранения регистра в контекстном коммутаторе, вызов функции блокирует регистры), или вы не хотите платить стоимость вызова функции.

Большинство компиляторов C поддерживают встроенную сборку, но синтаксис меняется. Он обычно вводится по ключевым словам asm, _asm, __asm или __asm__. В дополнение к самому ассемблеру встроенная сборка может содержать дополнительный код, который позволяет передавать значения между сборкой и C (например, запрашивая, чтобы значение локальной переменной копировалось в регистр при записи) или объявить что код сборки сжимает или сохраняет определенные регистры.

Ответ 4

asm ("") и __ asm __ являются действительным использованием. В принципе, вы можете использовать __ asm __, если ключевое слово asm конфликтует с чем-то в вашей программе. Если у вас несколько инструкций, вы можете написать по одной строке в двойных кавычках, а также суффикс a \n и \t. Это связано с тем, что gcc отправляет каждую команду как string в качестве (GAS), а с помощью новой строки/вкладки вы можете отправлять правильно отформатированные строки на ассемблер. Фрагмент кода в вашем вопросе базовый встроенный.

В базовой встроенной сборке есть только инструкции. В расширенной сборке вы также можете указать операнды . Он позволяет вам указать входные регистры, регистры вывода и список скребковых регистров. Не обязательно указывать регистры для использования, вы можете оставить это в GCC и, вероятно, лучше вписываться в схему оптимизации GCC. Примером расширенного asm является:

__asm__ ("movl %eax, %ebx\n\t"
           "movl $56, %esi\n\t"
           "movl %ecx, $label(%edx,%ebx,$4)\n\t"
           "movb %ah, (%ebx)");

Обратите внимание, что '\n\t' в конце каждой строки, кроме последней, и каждая строка заключена в кавычки. Это связано с тем, что gcc отправляет каждую команду как строку, как я упоминал ранее. Комбинация новой строки/вкладки требуется, чтобы линии были поданы так, как в правильном формате.