Я работаю над кодом, который будет собран в моем проекте Rust. Мы с коллегой обсуждаем соглашения о вызовах, и мне не удалось найти для этого точные ресурсы.
Насколько я понимаю, соглашения о вызовах в Rust не определены. Итак, при вызове ассемблера я должен использовать соглашение о вызовах C, чтобы что-то было известно.
Я работаю между тремя функциями:
unsafe extern "C" fn f(a) -> b
— это голая функция, вызываемая только из Rust. Он полностью ассемблерный и предполагает, что параметры и возвращаемые значения выполняются, как в C (в стеке и помещаются в %eax).
Поскольку он помечен как C и вызывается из Rust, компилятор гарантирует, что где бы он ни вызывался, для него будет использоваться соглашение о вызовах C.
unsafe extern "C" fn g()
также является голой функцией. Он никогда не вызывается из Rust. Лишь иногда f
возвращается к этой функции (то есть ret
из f
выдает адрес g
).
Хотя это не настоящая функция C (я знаю, что она вызывается из f
и сразу использует регистры, а не f
оставленные значения), она всегда будет вызывать функцию ржавчины. По сути, все это помещает параметры в стек, а затем от call
до h
(с помощью sym h
).
unsafe fn h
— это последняя функция здесь. Он вызывается только из g
через это sym
во встроенной сборке.
Итак, h
следует ожидать вызова с использованием соглашений C, но он может вернуться с соглашениями Rust (фактически h
никогда не возвращается).
Тогда мой вопрос:
Будет ли использование call
и sym h
из моей функции C, g, предупреждать компилятор Rust о необходимости последовательного использования соглашений о вызовах C в h
?
Ранее я пометил h
как extern "C"
, несмотря на то, что это чистый код ржавчины. На мой взгляд, это гарантирует, что компилятор уважает аргументы h
в стиле C. Но код, похоже, работает в обе стороны. Я не хочу, чтобы это изменилось, если в будущем Rust решит оптимизировать вызов h
по-другому (и неправильно).
Что касается h
, я получаю предупреждения при использовании extern "C"
. Одна вещь, к которой я обращаюсь h
, — это указатель на функцию Rust, а затем я вызываю его внутри h
. Это генерирует предупреждение о FFI, если h
отмечен C.
Использование call
во встроенном ассемблере не гарантирует, что функция будет использовать соглашение о вызовах C (фактически, компилятор вообще не имеет представления о встроенном ассемблере). Вам нужно использовать extern "C"
. Использование соглашения о вызовах Rust может показаться эффективным (иногда соглашения о вызовах эквивалентны), но это не гарантировано.
Если вы передаете указатели на функции h()
, они также должны быть extern "C"
, даже если они не вызываются из сборки. Насколько я могу судить, нет никакой гарантии относительно ABI функций соглашения о вызовах Rust, поэтому вы не можете даже передать их, поэтому компилятор вас предупреждает.
Если h()
не возвращает, вы можете пометить его как возвращаемый тип !
, что заставит компилятор убедиться, что он действительно не возвращает (а если он вызван из Rust, это также позволит ему использовать эту информацию).
Если у вас нет причин избегать
extern "C"
, то я бы порекомендовал. Я не могу сказать наверняка, но насколько я понимаю, Rust обычно помещает значения в регистры, где это возможно, следуя тому же соглашению, что и C - но это вполне может измениться.