Что такое регистры сохраненных абонентов и абонентов?

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

Я использую 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 после вызова.

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

6 ответов

Решение

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

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

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

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

Надеюсь, поможет.

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

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

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

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

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

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


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

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

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

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

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

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

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

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


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


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

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

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

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

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

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

Регистры сохраненного вызывающего абонента (также известные как энергозависимые или закрытые)

  • Значения в регистрах, сохраненных вызывающим абонентом, кратковременны и не сохраняются от вызова к вызову  
  • Он содержит временные (т.е. краткосрочные) данные

Сохраненные по вызову (также известные как энергонезависимые или сохраненные по вызову) регистры

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

Я не совсем уверен, добавляет ли это что-нибудь, кроме

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

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

Проблема с приведенным выше определением заключается в том, что, например, в Wikipedia cdecl говорится: eax, ecx а также edxсохраняются вызывающие абоненты, а остальные сохраняются, это предполагает, что вызывающий абонент должен сохранить все 3 из этих регистров, хотя это могло бы быть не так, если бы ни один из этих регистров не использовался вызывающим абонентом в первую очередь. В этом случае "сохраненный" вызывающий абонент становится неправильным, но "call clobbered" по-прежнему применяется правильно. То же самое и с "остальными", называемыми сохраненными вызываемыми абонентами. Это подразумевает, что все другие регистры x86 будут сохранены и восстановлены вызываемым пользователем, если это не так, если некоторые из регистров никогда не используются в вызове. С cdecl,eax:edxможет использоваться для возврата 64-битного значения. Я не уверен почемуecx также вызывающий абонент сохраняется при необходимости, но это так.

Я добавляю к этому спустя почти 3 года после заданных вопросов. Короче говоря, вызывающая сторона сохраняет следующие регистры --> rdi, rsi, rdx, rcx, r8, r9. Вызываемый сохраняет следующие регистры --> rbx, rbp, r12, r13, r14.

Примечание. Я ответил с точки зрения архитектуры x86-64.

Ссылка: Компьютерные системы: взгляд программиста, Рэндал Брайант (автор), Дэвид О'Халларон (автор)

Другие вопросы по тегам