Rust: Функции Rust, которые можно вызывать из C

Созданный на 2 февр. 2012  ·  16Комментарии  ·  Источник: rust-lang/rust

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

Вот минимальное решение для создания функций в Rust, которые можно вызывать из кода C. Суть в следующем: 1) у нас есть еще один вид объявления функции, 2) эта функция не может быть вызвана из кода Rust, 3) ее значение можно принять как непрозрачный небезопасный указатель, 4) она запекает магию переключения стека и адаптируется из C ABI в Rust ABI.

Объявления функций C-to-Rust (crust):

crust fn callback(a: *whatever) {
}

Получение небезопасного указателя на функцию C ABI:

let callbackptr: *u8 = callback;

Мы также можем определить какой-нибудь тип специально для этой цели.

Реализация компилятора:

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

  • Сгенерируйте функцию C ABI, используя объявленную подпись
  • Создайте функцию прокладки, которая принимает аргументы C в структуре
  • Функция C помещает аргументы в структуру
  • Функция C вызывает upcall_call_shim_on_rust_stack со структурой аргументов и адресом функции прокладки
  • Сгенерируйте функцию Rust ABI, используя объявленную подпись
  • Функция прокладки извлекает аргументы из структуры и вызывает функцию Rust.

Реализация во время выполнения:

Чтобы это произошло, среда выполнения должна измениться несколькими способами:

  • Новый вызов для возврата к стеку Rust
  • Задачи должны поддерживать стек контекстов Rust и контекстов C
  • Требуется стратегия, чтобы справиться с ошибкой после повторного входа в стек Rust.
  • Требуется стратегия, чтобы справиться с уступкой после повторного входа в стек Rust.

Отказ:

Мы не можем просто выбросить исключение после повторного входа в стек Rust, потому что нет гарантии, что собственный код может быть размотан с помощью исключений C ++. Очевидно, что в этом сценарии язык Go просто пропустит все собственные кадры, попутно пропуская все. Вместо этого мы прервемся - если пользователь хочет избежать катастрофического сбоя, он должен использовать обратный вызов Rust для отправки сообщения и немедленного возврата.

Урожайность:

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

1) Урожайность отличается после повторного входа в стек Rust и просто блокируется. Задачи, которые хотят это сделать, должны убедиться, что у них есть собственный планировщик (# 1721).
2) Вместо запуска собственного кода с использованием стека планировщика задачи будут извлекать стеки C из пула, расположенного в каждом планировщике. Каждый раз, когда задача повторно входит в стек C, она проверяет, есть ли он у нее, и повторно использует его, в противном случае она запрашивает новый у планировщика. Это позволило бы коду Rust всегда работать нормально, не связывая планировщик.

Я предпочитаю второй вариант.

См. Также # 1508

A-debuginfo A-runtime A-typesystem E-easy

Самый полезный комментарий

Пожалуйста, простите это воскрешение, но эта проблема связана с y-комбинатором и несколькими другими сайтами, и недавно у меня был один нуб, спросивший об этом, поэтому я отмечаю, что в соответствии с этой проблемой и последующими изменениями, вызывая Rust из C просто :

#[no_mangle]
pub extern fn hello_rust() -> *const u8 {
    "Hello, world!\0".as_ptr()
}
#include "stdio.h"
const char *hello_rust(void);
int main(void) {
    printf("%.32s\n", hello_rust());
}

Все 16 Комментарий

Извините, что вмешался, но я хотел бы подчеркнуть, что вызов функций C должен быть быстрым, как в _blazing_ fast. Если я хочу написать игру в ржавчине с использованием библиотеки C, такой как Allegro, SDL или Opengl, это необходимо. В противном случае игра будет тормозить в коде рендеринга, где много вызовов C, что недопустимо. Компилятор языка Go по умолчанию с cgo имеет такие проблемы.

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

Кроме того, не было бы идеей использовать «native fn» вместо «crust fn» или это имеет другое запланированное значение?

@beoran, у вас это задом наперед. Мы говорим о том, что C вызывает Rust. Вызов функций C из Rust уже происходит довольно быстро (можно было бы сделать несколько быстрее).

Да я вижу. Могу ли я как-нибудь помочь ускорить вызов C из ржавчины?

@beoran, как я уже сказал, это как бы ортогонально этой проблеме ... но, вероятно, лучшее, что вы могли бы сделать, - это составить тест, показывающий, насколько производительность неадекватна. :)

Хорошо, я сделаю это, когда уйду достаточно далеко в обертке Allegro, чтобы сравнить накладные расходы на его вызов из rust Rust с вызовом из C. Я пока оставлю эту проблему в покое и открою новую, как только у меня будет эталон.

Можно ли избежать одной из копий аргументов, если функция C записывает непосредственно в стек Rust, как это делают в настоящее время вызовы Rust-> C?

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

Я считаю, что наши вызовы C в настоящее время копируют аргументы в структуру в стеке Rust, а затем копируют эту структуру в стек C.

@pcwalton Rust-> C в настоящее время не записывает напрямую в стек C, потому что это характерно для i386. Я хотел избежать написания кода, специфичного для конкретного соглашения о вызовах, даже ценой некоторой производительности, чтобы заставить работать 64-битную версию. (Я думаю, что сейчас такая оптимизация может иметь смысл - особенно, поскольку # 1402 указывает на то, что LLVM на самом деле не полностью обрабатывает соглашения о вызовах _ в любом случае_)

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

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

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

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

Итак, когда задача верхнего уровня терпит неудачу, она передает ошибки своим дочерним элементам? Думаю, я не понимаю нашу модель распространения ошибок, я думал, что она пошла с нуля. Кажется, что код, работающий в стеках C, должен иметь возможность запускаться в неконтролируемой задаче или что-то в этом роде.

По сути, он действует так, как будто "main" контролируется ядром, поэтому, если main не работает, все терпит неудачу.

Я называю это готовым. Есть небольшая очистка, и я зарегистрировал отдельные ошибки для оставшихся проблем.

Пожалуйста, простите это воскрешение, но эта проблема связана с y-комбинатором и несколькими другими сайтами, и недавно у меня был один нуб, спросивший об этом, поэтому я отмечаю, что в соответствии с этой проблемой и последующими изменениями, вызывая Rust из C просто :

#[no_mangle]
pub extern fn hello_rust() -> *const u8 {
    "Hello, world!\0".as_ptr()
}
#include "stdio.h"
const char *hello_rust(void);
int main(void) {
    printf("%.32s\n", hello_rust());
}
Была ли эта страница полезной?
0 / 5 - 0 рейтинги