Rust: Fungsi karat yang dapat dipanggil dari C

Dibuat pada 2 Feb 2012  ·  16Komentar  ·  Sumber: rust-lang/rust

Kami memiliki banyak skenario sekarang di mana orang yang membuat binding ingin dapat memberikan panggilan balik yang dapat dipanggil oleh fungsi C. Solusi saat ini adalah menulis fungsi C asli yang menggunakan beberapa API internal yang tidak ditentukan untuk mengirim pesan kembali ke kode Rust. Idealnya tidak melibatkan penulisan kode C.

Berikut adalah solusi minimal untuk membuat fungsi di Rust yang dapat dipanggil dari kode C. Intinya adalah: 1) kami memiliki jenis lain dari deklarasi fungsi, 2) fungsi ini tidak dapat dipanggil dari kode Rust, 3) nilainya dapat diambil sebagai penunjuk tidak aman buram, 4) itu memanggang dalam sihir peralihan tumpukan dan beradaptasi dari C ABI ke Rust ABI.

Deklarasi fungsi C-to-Rust (kerak):

crust fn callback(a: *whatever) {
}

Mendapatkan pointer tidak aman ke fungsi C ABI:

let callbackptr: *u8 = callback;

Kami juga dapat mendefinisikan beberapa tipe khusus untuk tujuan ini.

Implementasi kompiler:

Ini sebagian besar mudah, tetapi trans menjadi jelek. Dalam trans, pada dasarnya kita perlu melakukan kebalikan dari apa yang kita lakukan untuk fungsi mod asli:

  • Hasilkan fungsi C ABI menggunakan tanda tangan yang dideklarasikan
  • Hasilkan fungsi shim yang mengambil argumen C dalam sebuah struct
  • Fungsi C memasukkan argumen ke dalam struct
  • Fungsi C memanggil upcall_call_shim_on_rust_stack dengan struct argumen dan alamat fungsi shim
  • Hasilkan fungsi Rust ABI menggunakan tanda tangan yang dideklarasikan
  • Fungsi shim menarik argumen keluar dari struct dan memanggil fungsi Rust

Implementasi waktu proses:

Runtime harus berubah dalam beberapa cara untuk mewujudkannya:

  • Panggilan baru untuk beralih kembali ke tumpukan Rust
  • Tugas perlu mempertahankan setumpuk konteks Rust dan konteks C
  • Membutuhkan strategi untuk menghadapi kegagalan setelah memasuki kembali tumpukan Rust
  • Membutuhkan strategi untuk menangani hasil setelah memasuki kembali tumpukan Rust

Kegagalan:

Kami tidak bisa begitu saja melempar pengecualian setelah masuk kembali ke tumpukan Rust karena tidak ada jaminan kode asli dapat dibatalkan dengan pengecualian C++. Bahasa Go tampaknya hanya akan melewati semua bingkai asli dalam skenario ini, membocorkan semuanya di sepanjang jalan. Kami, sebaliknya akan membatalkan - jika pengguna ingin menghindari kegagalan bencana, mereka harus menggunakan panggilan balik Rust mereka untuk mengirim pesan dan segera kembali.

Menghasilkan:

Tanpa perubahan pada cara kami menangani tumpukan C, kami tidak dapat mengizinkan fungsi Rust beralih konteks ke penjadwal setelah memasukkan kembali tumpukan Rust dari kode C. Saya melihat dua solusi:

1) Hasil berbeda setelah memasuki kembali tumpukan Rust dan hanya memblokir. Tugas yang ingin melakukan ini harus memastikan mereka memiliki penjadwal sendiri (#1721).
2) Alih-alih menjalankan kode asli menggunakan tumpukan penjadwal, tugas akan memeriksa tumpukan C dari kumpulan yang terletak di setiap penjadwal. Setiap kali tugas masuk kembali ke tumpukan C, ia akan memeriksa apakah sudah ada dan menggunakannya kembali, jika tidak maka akan meminta yang baru dari penjadwal. Ini akan memungkinkan kode Rust untuk selalu menghasilkan secara normal tanpa mengikat penjadwal.

Saya lebih suka opsi kedua.

Lihat juga #1508

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

Komentar yang paling membantu

Maafkan kebangkitan ini, tetapi masalah ini ditautkan dari bagian y-combinator dan beberapa situs lain, dan saya baru-baru ini memiliki pertanyaan noob tentang hal itu, jadi saya perhatikan bahwa, per masalah ini & perubahan selanjutnya, memanggil Rust dari C sederhana :

#[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());
}

Semua 16 komentar

Maaf untuk melompat ke sini, tetapi saya ingin menekankan bahwa memanggil fungsi C harus cepat, seperti dalam _blazing_ fast. Jika saya ingin menulis game di rust menggunakan pustaka C seperti Allegro, SDL, atau Opengl, ini penting. Jika tidak, permainan akan melambat dalam kode rendering di mana ada banyak panggilan C, yang tidak dapat diterima. Kompiler bahasa Go default dengan cgo memiliki masalah seperti itu.

Jadi saya lebih suka solusi yang cepat, meskipun mungkin membatasi apa yang dapat dilakukan oleh fungsi di sisi Rust.

Juga, bukankah ide untuk menggunakan "fn asli" sebagai ganti "kerak fn" atau apakah itu memiliki arti lain yang direncanakan?

@beoran Anda memiliki ini mundur. Kita berbicara tentang C yang memanggil Rust. Memanggil fungsi C dari Rust sudah cukup cepat (bisa dibuat lebih cepat).

Ok aku paham. Apakah ada cara saya dapat membantu mempercepat panggilan C dari karat?

@beoran seperti yang saya katakan, ini agak ortogonal untuk masalah ini ... tapi mungkin hal terbaik yang dapat Anda lakukan adalah membuat tolok ukur yang menunjukkan bagaimana kinerjanya tidak memadai. :)

Oke, saya akan melakukannya ketika saya sudah cukup jauh dalam membungkus Allegro untuk membandingkan overhead memanggilnya dari rust Rust dengan memanggilnya dari C. Saya akan membiarkan masalah ini sendiri untuk saat ini dan saya akan membuka yang baru setelah saya memilikinya patokan.

Bisakah kita menghindari salah satu salinan argumen dengan meminta fungsi C menulis langsung ke tumpukan Rust, seperti yang dilakukan panggilan Rust->C saat ini?

Saya berharap salinan terakhir dari argumen dari shim struct dan ke dalam argumen dari fungsi rust akan dihilangkan melalui inlining. Saya tidak yakin apakah itu yang Anda maksud.

Saya percaya bahwa panggilan C kami saat ini menyalin argumen ke dalam struct di tumpukan Rust lalu menyalin struct itu ke tumpukan C.

@pcwalton Rust->C saat ini tidak menulis langsung ke dalam C stack karena itu khusus untuk i386. Saya ingin menghindari keharusan menulis kode yang khusus untuk konvensi pemanggilan tertentu, bahkan dengan harga beberapa kinerja, agar 64-bit berfungsi. (Saya pikir pengoptimalan seperti itu mungkin masuk akal sekarang --- terutama seperti yang ditunjukkan #1402 bahwa LLVM tidak benar-benar menangani konvensi pemanggilan sepenuhnya _anyway_)

Setelah membaca fungsi peralihan tumpukan, struct arg tidak disalin di seluruh tumpukan sama sekali, penunjuk ke tumpukan sebelumnya hanya diteruskan ke fungsi yang berjalan di tumpukan baru, yang sangat masuk akal.

Struktur arg tidak disalin, tetapi fungsi shim akan memuat nilai darinya dan mendorongnya kembali ke tumpukan baru. Kode lama yang digunakan untuk menulis nilai argumen langsung ke tumpukan target. Ini masuk akal pada i386 tetapi pada x86_64 jauh lebih rumit untuk mengetahui nilai mana yang akan masuk ke tumpukan dll.

Setelah beberapa pengujian, saya menemukan bahwa akan sangat sulit bagi fungsi kerak untuk menjamin bahwa mereka tidak gagal. Apa yang saat ini terjadi adalah, ketika tugas tingkat atas (seperti utama) gagal, setiap tugas dikatakan gagal, jadi segera setelah panggilan balik mencoba mengirim pesan (atau ketika kembali dari mengirim pesan) itu bisa berakhir gagal dan menyebabkan runtime dibatalkan secara tidak normal.

Saya kira kita dapat mengubah rust_task untuk mengabaikan permintaan pembunuhan setelah tugas masuk kembali ke tumpukan karat. Untuk tugas yang mengimplementasikan loop acara, mereka dapat mengintip beberapa port monitor mencari pesan yang menunjukkan bahwa runtime gagal dan mencari cara untuk mengakhiri dengan anggun.

Jadi ketika tugas tingkat atas gagal, itu menyebarkan kesalahan ke anak-anaknya? Saya kira saya tidak mengerti model propagasi kesalahan kami, saya pikir itu naik dari daun ke atas. Sepertinya kode yang berjalan pada tumpukan C harus dapat dijalankan dalam tugas yang tidak diawasi atau semacamnya.

Ini pada dasarnya bertindak seolah-olah 'utama' diawasi oleh kernel, jadi jika main gagal semuanya gagal.

Saya menyebut ini selesai. Ada sedikit pembersihan, dan saya telah mengajukan bug terpisah untuk masalah yang tersisa.

Maafkan kebangkitan ini, tetapi masalah ini ditautkan dari bagian y-combinator dan beberapa situs lain, dan saya baru-baru ini memiliki pertanyaan noob tentang hal itu, jadi saya perhatikan bahwa, per masalah ini & perubahan selanjutnya, memanggil Rust dari C sederhana :

#[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());
}
Apakah halaman ini membantu?
0 / 5 - 0 peringkat