Подтвердить что ты не робот

OCaml вызов конвенции: это точное резюме?

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

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

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


Распределение регистров (от /asmcomp/amd64/proc.ml)

Для вызова функций OCaml

  • Первые 10 целых и указательных аргументов передаются в регистры rax, rbx, rdi, rsi, rdx, rcx, r8, r9, r10 и r11
  • Первые 10 аргументов с плавающей запятой передаются в регистры xmm0 - xmm9
  • Дополнительные аргументы переносятся в стек (leftmost-first-in?), поплавки и ints и указатели перемежаются
  • Указатель ловушки (см. Исключения ниже) передается в r14
  • Указатель выделения (предположительно для небольшой кучи, описанный в этом сообщении в блоге) передается в r15
  • Возвращаемое значение передается обратно в rax, если оно является целым числом или указателем, а в xmm0, если оно является float
  • Все регистры - это сохранение звонящего?

Для вызова функций C используется стандартное соглашение amd64 C:

  • Первые шесть аргументов integer и pointer передаются в rdi, rsi, rdx, rcs, r8 и r9
  • Первые восемь аргументов float передаются в xmm0 - xmm7
  • Дополнительные аргументы помещаются в стек
  • Возвращаемое значение возвращается в rax или xmm0
  • Регистры rbx, rbp и r12 - r15 - это callee-save

Обратный адрес (от /asmcomp/amd64/emit.mlp)

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

Исключения (от /asmcomp/linearize.ml)

Код try (...body...) with (...handler...); (...rest...) линеаризуется следующим образом:

Lsetuptrap .body
(...handler...)
Lbranch .join
Llabel .body
Lpushtrap
(...body...)
Lpoptrap
Llabel .join
(...rest...)

а затем выбрано как сборка (это пункты назначения справа):

call .body
(...handler...)
jmp .join
.body:
pushq %r14
movq %rsp, %r14
(...body...)
popq %r14
addq %rsp, 8
.join:
(...rest...)

Где-то в теле есть линеаризованный код операции Lraise, который испускается как эта точная сборка:

movq %r14, %rsp
popq %r14
ret

Это действительно аккуратно! Вместо этого бизнеса setjmp/longjmp мы создаем фиктивный фрейм, обратный адрес которого является обработчиком исключений и единственным локальным из которых является предыдущий такой фиктивный фрейм. /asmcomp/amd64/proc.ml имеет комментарий, вызывающий $r14 "указатель на ловушку", поэтому я буду называть этот фиктивный фрейм ловушкой. Когда мы хотим создать исключение, мы устанавливаем указатель стека на самый последний ловушечный кадр, перед этим устанавливаем указатель ловушки на ловушку, а затем "возвращаемся" в обработчик исключений. И я уверен, если обработчик исключений не может справиться с этим исключением, он просто ререйзит.

Исключение составляет% eax.

4b9b3361

Ответ 1

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

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

¹: для несамоходных вызовов это работает только тогда, когда не слишком много аргументов, и поэтому нам не нужно проливать. Если требуется распределение стека, вызов превращается в вызов без хвоста.

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

Я также соглашаюсь на "leftmost-first-in": аргументы пересекаются по порядку calling_conventions в proc.ml, хранятся в порядке смещения на slot_offset в emit.mlp; они вычисляются справа налево, но возвращаются в порядке, в selectgen.ml.

Ответ 2

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