Что такое регистры сохраненных абонентов и абонентов? - PullRequest
37 голосов
/ 14 февраля 2012

У меня возникли проблемы с пониманием разницы между сохраненными регистрами вызывающих и вызываемых абонентов и когда что использовать.

Я использую MSP430 :

процедура:

mov.w #0,R7 
mov.w #0,R6 
add.w R6,R7 
inc.w R6 
cmp.w R12,R6 
jl l$loop 
mov.w R7,R12
ret

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

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

Правильно ли мое понимание? Я скучаю по другим вещам?

Ответы [ 4 ]

85 голосов
/ 28 апреля 2013

Регистры, сохраненные вызывающим абонентом (регистры AKA volatile или clobbered ) используются для хранения временных количеств, которые необходимы не сохраняется при звонках.

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

Вполне нормально, что call уничтожает временные значения в этих регистрах.

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

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

12 голосов
/ 14 февраля 2012

Callee vs caller сохранено - соглашение о том, кто отвечает за сохранение и восстановление значения в регистре при вызове. ВСЕ регистры являются «глобальными» в том смысле, что любой код в любом месте может видеть (или изменять) регистр, и эти изменения будут видны любым последующим кодом в любом месте. Смысл соглашений о сохранении регистров состоит в том, что код не должен изменять определенные регистры, поскольку другой код предполагает, что значение не изменено.

В вашем примере кода ни один из регистров не сохраняется, так как он не пытается сохранить или восстановить значения регистра. Однако может показаться, что это не целая процедура, поскольку она содержит ветку с неопределенной меткой (l$loop). Таким образом, это может быть фрагмент кода из середины процедуры, который обрабатывает некоторые регистры как сохраняемые; вы просто пропускаете инструкции сохранения / восстановления.

3 голосов
/ 17 мая 2019

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

Или вы должны понимать, что «сохраненный вызывающим абонентом» означает «каким-то образом сохраненный , если *»1004 * вы хотите значение позже ".

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

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

Кроме того, эти термины отличаются более чем на одну букву.

Термины энергозависимые / энергонезависимые довольно хороши, по аналогии с хранилищем, которое теряет свою ценность при потере мощности или нет (например, DRAM против Flash).Но ключевое слово C volatile имеет совершенно иное техническое значение, поэтому при описании соглашений о вызовах в Си это недостаток «(non) -volatile».


  • Call-clobbered, он же сохраненных вызывающих абонентов или volatile регистры хороши для временных / временных значений, которые не нужны после следующего вызова функции.

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

С точки зрения вызывающего абонента, call foo уничтожает (так называемые clobbers) все регистры с закрытым вызовом или, по крайней мере,Вы должны предположить, что это так.

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

Некоторые компиляторы, которые выполняют межпроцедурную оптимизацию, могут также создавать определения функций только для внутреннего использования, которые не следуют ABI, используя пользовательскиесоглашение о вызовах.

  • Сохранение вызовов , иначе сохранение вызовов или энергонезависимые регистры сохраняют свои значения по всемувызовы функций .Это полезно для переменных цикла в цикле, который выполняет вызовы функций, или вообще для чего-либо в неконечной функции в целом.

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

Функция, которая может выиграть от использования большого количества регистров, может сохранять / восстанавливать некоторые регистры, сохраняемые при вызове, просто так, чтобы она могла использовать их в качестве большего количества временных файлов, даже если онане делает никаких вызовов функций.Обычно вы делаете это только после исчерпания регистров с ограниченным вызовом для использования, потому что сохранение / восстановление обычно стоит push / pop в начале / конце функции.(Или, если ваша функция имеет несколько путей выхода, в каждом из них pop.)


Название «сохраненный вызывающим абонентом» вводит в заблуждение: у вас нет , чтобы специально сохранять / восстанавливать их.Обычно в вашем коде есть значения, которые должны выдерживать вызов функции в сохраняемых вызовах регистрах, или где-то в стеке, или в другом месте, откуда вы можете перезагрузить.Разрешить call уничтожить временные значения нормально.


ABI или соглашение о вызовах определяет, какие именно

См., Например, Какие регистры сохраняются в Linux x86-64 вызов функции для x86-64 System V ABI.

Кроме того, регистры передачи аргументов всегда закрыты вызовом во всех соглашениях о вызовах функций, о которых я знаю.См. Сохраняются ли регистры вызывающих абонентов rdi и rsi или сохраняемые регистры вызываемых абонентов?

Но соглашения о вызовах системного вызова обычно обеспечивают сохранение всех регистров, кроме возвращаемого значения.(Обычно включает даже коды условий / флаги.) См. Каковы соглашения о вызовах для системных вызовов UNIX и Linux на i386 и x86-64

0 голосов
/ 13 июля 2019

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

...