понимание встроенной функции ассемблера GCC - PullRequest
0 голосов
/ 21 октября 2019

Я напишу свои предположения (основываясь на моих исследованиях) в приведенном ниже вопросе. Я предполагаю, что в моих сборках есть ошибки, помимо самого вопроса:

Я ищу какой-то код, написанный для ARM:

эта функция (взята из кода порта FreeRTOS):

portFORCE_INLINE static uint32_t ulPortRaiseBASEPRI(void)
{
    uint32_t ulOriginalBASEPRI, ulNewBASEPRI;

    __asm volatile("    mrs %0, basepri                                         \n"
                   "    mov %1, %2                                              \n"
                   "    msr basepri, %1                                         \n"
                   "    isb                                                     \n"
                   "    dsb                                                     \n"
                   : "=r"(ulOriginalBASEPRI), "=r"(ulNewBASEPRI)
                   : "i"(configMAX_SYSCALL_INTERRUPT_PRIORITY));

    /* This return will not be reached but is necessary to prevent compiler
    warnings. */
    return ulOriginalBASEPRI;
}

Я понимаю, что в gcc "= r" является выходным операндом. поэтому мы сохраняем значения из asm в переменную C

теперь код в моем понимании эквивалентен:

ulOriginalBASEPRI = basepri
ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY
basepri = ulNewBASEPRI

я понимаю, что мы возвращаем исходное значение BASEPRI, так что это первая строка. однако я не понял, почему мы назначаем переменную ulNewBASEPRI, а затем используем ее в инструкции MSR.

, поэтому я посмотрел в наборе команд ARMV7 и увидел следующее: enter image description here

Я предполагаю, что в инструкции большого пальца нет (немедленного MSR), и «Кодирование A1» означает, что оно только в режиме инструкции постановки на охрану. поэтому мы должны использовать выходной операнд = r, чтобы позволить asembler автоматически выбирать регистр для нашей переменной Я прав?


РЕДАКТИРОВАТЬ: игнорировать этот раздел, потому что я пропустилдвоеточия

: "i"(configMAX_SYSCALL_INTERRUPT_PRIORITY));

, насколько я понимаю, шаблон сборки :

   asm ( assembler template 
       : output operands                  /* optional */
       : input operands                   /* optional */
       : list of clobbered registers      /* optional */
       );

- это не "i", а просто означает (непосредственное) или постоянное вассемблер?
означает ли это, что третье двоеточие предназначено не только для списка клоббера?
если это так, не лучше ли найти ограничение "i" во входных операндах?
РЕДАКТИРОВАТЬ: игнорировать этот раздел, потому что я пропустил двоеточия


я понимаю isb, dsb - это барьер памяти, но я действительно не понимаю описания их. что они на самом деле делают? что произойдет, если мы удалим инструкцию DSB или ISB, например .?

Ответы [ 2 ]

2 голосов
/ 21 октября 2019

, поэтому мы должны использовать выходной операнд = r, чтобы ассемблер автоматически выбирал регистр для нашей переменной. Я прав?

Да, но это компилятор что делает регистр распределения. Он просто заполняет %[operand] в строке шаблона asm как текстовую подстановку и передает , что ассемблеру.

В качестве альтернативы, вы могли бы жестко кодировать aконкретный регистр в строке шаблона asm и используйте локальную переменную register-asm, чтобы убедиться, что ограничение выбрано "=r". Или используйте "=m" операнд вывода памяти и str результат в него, и объявите Clobber для любых регистров, которые вы использовали. Но эти альтернативы, очевидно, ужасны по сравнению с простым сообщением компилятору о том, как ваш блок asm может генерировать вывод.


Я не понимаю, почему в комментарии говорится, что оператор return не выполняется:

   /* This return will not be reached but is necessary to prevent compiler
   warnings. */
   return ulOriginalBASEPRI;

Повышение basepri ( ARM docs ) до большего числа может позволить обработчику прерываний запускаться сразу, перед более поздними инструкциями, но если это исключение когда-либо вернется, выполнение будетв конечном итоге достичь C за пределами оператора asm. В этом весь смысл сохранения старого basepri в регистр и наличия выходного операнда для него, я полагаю.

(я предполагал, что «поднимать» означало большее число = допускается больше прерываний. Но Росскомментирует, что он никогда не допустит больше прерываний, они "поднимают планку" = меньшее число = меньше разрешенных прерываний.)

Если выполнение действительно никогда не закончится, конецваш ассм, вы должны сообщить об этом компилятору . Существует asm goto, но для этого нужен список возможных целей ветвления. Руководство GCC гласит:

GCC предполагает, что выполнение asm переходит к следующему оператору (если это не так, рассмотрите возможность использования встроенного __builtin_unreachable() после оператора asm).

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


Itможет быть хорошей идеей использовать "memory" clobber, чтобы убедиться, что компилятор синхронизирует содержимое памяти с абстрактной машиной C . (По крайней мере, для переменных, отличных от локальных, к которым может обращаться обработчик прерываний). Обычно это желательно в отношении инструкций asm-барьера, таких как dsb, но, похоже, здесь нам, возможно, не нужно быть барьером памяти SMP, просто о последовательном выполнении после изменения basepri? Я не понимаю, почему это необходимо, но если вам все-таки стоит подумать о том, является ли переупорядочивание доступа к памяти во время компиляции вокруг оператора asm проблемой или нет.

You 'Использовать третий разделенный двоеточиями раздел в операторе asm (после входных данных) : "memory"

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

// actual C source
  global_var = 1;
  uint32_t oldpri = ulPortRaiseBASEPRI();
  global_var = 2;

может оптимизировать (посредством устранения мертвых хранилищ) в asm, который работает следующим образом

// possible asm
  global_var = 2;
  uint32_t oldpri = ulPortRaiseBASEPRI();
  // or global_var = 2; here *instead* of before the asm
2 голосов
/ 21 октября 2019

Относительно различий в наборе команд ARM / Thumb на msr: вы должны быть в состоянии ответить на этот вопрос самостоятельно из документации. ;-) Это только 2 страницы спустя. Редактировать: В главе A8.1.3 связанного руководства четко указано, как кодируются документированные инструкции.

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

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

...