Rust: وظائف الصدأ التي يمكن استدعاؤها من C.

تم إنشاؤها على ٢ فبراير ٢٠١٢  ·  16تعليقات  ·  مصدر: rust-lang/rust

لدينا الكثير من السيناريوهات الآن حيث يريد الأشخاص الذين يقومون بإنشاء روابط أن يكونوا قادرين على توفير رد اتصال يمكن لوظيفة C. الحل الحالي هو كتابة دالة C أصلية تستخدم بعض واجهات برمجة التطبيقات الداخلية غير المحددة لإرسال رسالة مرة أخرى إلى كود Rust. من الناحية المثالية ، يتضمن عدم كتابة كود C.

فيما يلي حل بسيط لإنشاء وظائف في Rust يمكن استدعاؤها من كود C. الجوهر هو: 1) لدينا نوع آخر من إعلان الوظيفة ، 2) لا يمكن استدعاء هذه الوظيفة من كود Rust ، 3) يمكن اعتبار قيمتها مؤشرًا غير شفاف غير آمن ، 4) تخبز في سحر تبديل المكدس وتتكيف من C ABI إلى Rust ABI.

إعلانات وظائف C-to-Rust (القشرة):

crust fn callback(a: *whatever) {
}

الحصول على مؤشر غير آمن لوظيفة C ABI:

let callbackptr: *u8 = callback;

يمكننا أيضًا تحديد نوع ما خصيصًا لهذا الغرض.

تنفيذ المترجم:

إنه أمر بسيط في الغالب ، لكن المتحولين يصبح قبيحًا. في Trans ، سنحتاج إلى القيام بشكل أساسي بعكس ما نفعله لوظائف التعديل الأصلية:

  • قم بإنشاء دالة C ABI باستخدام التوقيع المعلن
  • قم بإنشاء دالة shim تأخذ وسيطات C في بنية
  • تقوم الدالة C بحشو الحجج في بنية
  • تستدعي الدالة C upcall_call_shim_on_rust_stack مع بنية الوسائط وعنوان وظيفة shim
  • قم بإنشاء دالة Rust ABI باستخدام التوقيع المعلن
  • تسحب وظيفة shim الحجج من البنية وتستدعي وظيفة Rust

تنفيذ وقت التشغيل:

يجب أن يتغير وقت التشغيل بعدة طرق لتحقيق ذلك:

  • دعوة جديدة للعودة إلى كومة الصدأ
  • تحتاج المهام إلى الحفاظ على مجموعة من سياقات Rust وسياقات C
  • يحتاج إلى إستراتيجية للتعامل مع الفشل بعد إعادة الدخول إلى مجموعة Rust
  • يحتاج إلى إستراتيجية للتعامل مع العائد بعد إعادة الدخول إلى كومة الصدأ

بالفشل:

لا يمكننا ببساطة طرح استثناء بعد إعادة الدخول إلى مكدس Rust لأنه لا يوجد ضمان بإمكانية فك الشفرة الأصلية مع استثناءات C ++. من الواضح أن لغة Go ستتخطى جميع الإطارات الأصلية في هذا السيناريو ، مما يؤدي إلى تسريب كل شيء على طول الطريق. بدلاً من ذلك ، سنجهض - إذا أراد المستخدم تجنب الفشل الكارثي ، فيجب عليه استخدام رد الاتصال Rust لإرسال رسالة والعودة على الفور.

الاستسلام:

بدون تغييرات على الطريقة التي نتعامل بها مع حزم C ، لا يمكننا السماح لوظائف Rust بتبديل السياق إلى المجدول بعد إعادة إدخال حزمة Rust من كود C. أرى حلين:

1) يختلف العائد بعد إعادة دخول كومة الصدأ والكتل ببساطة. يجب أن تتأكد المهام التي تريد القيام بذلك من أن لديها برنامج الجدولة الخاص بها (# 1721).
2) بدلاً من تشغيل التعليمات البرمجية الأصلية باستخدام مكدس المجدول ، ستفحص المهام مجموعات C من مجموعة موجودة في كل مجدول. في كل مرة تعيد المهمة الدخول إلى المكدس C ، سوف تتحقق مما إذا كانت تحتوي بالفعل على واحدة وتعيد استخدامها ، وإلا فإنها ستطلب مهمة جديدة من المجدول. سيسمح هذا لكود Rust أن ينتج بشكل طبيعي دائمًا دون تقييد المجدول.

أنا أفضل الخيار الثاني.

راجع أيضًا # 1508

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

التعليق الأكثر فائدة

أرجو أن تغفر هذه القيامة ، لكن هذه المشكلة مرتبطة بقطعة y-combinator وبعض المواقع الأخرى ، وقد كان لدي مؤخرًا مستجد يسأل عنها ، لذلك أنا أشير إلى أنه في هذه المسألة والتغييرات اللاحقة ، اتصل بـ 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 لديه مثل هذه المشاكل.

لذلك أفضل حلًا سريعًا ، على الرغم من أنه قد يقيد ما يمكن أن تقوم به الوظيفة الموجودة على جانب الصدأ.

أيضًا ، ألن تكون فكرة استخدام "أصلي fn" بدلاً من "crust fn" أم أن هذا له معنى مخطط آخر؟

beoran لديك هذا إلى الوراء. نحن نتحدث عن C استدعاء Rust. يعد استدعاء وظائف C من Rust سريعًا بالفعل (يمكن جعله أسرع إلى حد ما).

حسنًا ، فهمت. هل هناك أي طريقة يمكنني من خلالها تسريع استدعاء C من الصدأ؟

beoran كما قلت ، إنه نوع من التعامد لهذه المشكلة ... ولكن ربما يكون أفضل شيء يمكنك القيام به هو وضع معيار يوضح كيف أن الأداء غير ملائم. :)

حسنًا ، سأفعل ذلك عندما أقوم بلف Allegro بعيدًا بما يكفي لمقارنة النفقات العامة لاستدعائها من الصدأ Rust باستدعاءها من C. سأترك هذه المشكلة بمفردها في الوقت الحالي وسأفتح واحدة جديدة بمجرد المؤشر.

هل يمكننا تجنب إحدى نسخ الوسائط من خلال جعل الدالة C تكتب مباشرة إلى مكدس Rust ، كما تفعل استدعاءات Rust-> C حاليًا؟

آمل أن يتم التخلص من النسخة النهائية من الحجج من هيكل الرقائق إلى حجج وظيفة الصدأ من خلال التضمين. لست متأكدًا مما إذا كان هذا هو ما تشير إليه.

أعتقد أن مكالمات C الخاصة بنا تقوم حاليًا بنسخ الحجج إلى بنية على مكدس Rust ثم نسخ هذا الهيكل إلى المكدس C.

pcwalton Rust-> C لا تكتب حاليًا مباشرة في مكدس C لأن ذلك خاص بـ i386. أردت تجنب الاضطرار إلى كتابة رمز خاص باتفاقية اتصال معينة ، حتى بسعر بعض الأداء ، من أجل الحصول على عمل 64 بت. (أعتقد أن مثل هذه التحسينات قد تكون منطقية الآن ، على الرغم من - خاصة وأن # 1402 يشير إلى أن LLVM لا تتعامل حقًا مع اصطلاحات الاستدعاء تمامًا _ بأي حال_)

بعد قراءة وظيفة تبديل المكدس ، لا يتم نسخ بنية الوسيطة عبر المكدس على الإطلاق ، ويتم تمرير المؤشر إلى المكدس السابق للتو إلى الوظيفة التي تعمل على المكدس الجديد ، وهو أمر منطقي تمامًا.

لم يتم نسخ بنية arg ، لكن دالة الرقائق ستحمل قيمًا منه وتعيد دفعها إلى المكدس الجديد. الكود القديم المستخدم لكتابة قيم الوسيطات حرفيًا مباشرة في المكدس الهدف. كان هذا منطقيًا في i386 ولكن في x86_64 ، يكون الأمر أكثر تعقيدًا لمعرفة القيم التي سيتم وضعها في المكدس وما إلى ذلك.

بعد إجراء بعض الاختبارات ، وجدت أنه سيكون من الصعب جدًا على وظائف القشور ضمان عدم فشلها. ما يحدث حاليًا هو أنه عندما تفشل مهمة من المستوى الأعلى (مثل المهمة الرئيسية) ، يُطلب من كل مهمة أن تفشل ، وبمجرد أن يحاول رد الاتصال إرسال رسالة (أو عندما يعود من إرسال رسالة) ، فقد ينتهي به الأمر بالفشل وتسبب في إجهاض وقت التشغيل بشكل غير طبيعي.

أعتقد أنه يمكننا تغيير rust_task لتجاهل طلبات القتل بمجرد عودة المهام إلى كومة الصدأ. بالنسبة للمهام التي تنفذ حلقات الأحداث ، يمكنهم إلقاء نظرة خاطفة على بعض منافذ الشاشة بحثًا عن رسالة تشير إلى فشل وقت التشغيل ومعرفة كيفية الإنهاء بأمان.

إذن ، عندما تفشل مهمة المستوى الأعلى ، فإنها تنشر أخطاء لأبنائها؟ أعتقد أنني لا أفهم نموذج انتشار الخطأ الخاص بنا ، اعتقدت أنه ذهب من الأوراق لأعلى. يبدو أن الكود الذي يعمل على C Stacks يجب أن يكون قادرًا على العمل في مهمة غير خاضعة للإشراف أو شيء من هذا القبيل.

إنه يتصرف بشكل أساسي كما لو أن النواة تشرف على "main" ، لذلك إذا فشل main في فشل كل شيء.

أنا أسمي هذا انتهى. هناك القليل من التنظيف ، وقد قدمت أخطاء منفصلة للمشكلات المتبقية.

أرجو أن تغفر هذه القيامة ، لكن هذه المشكلة مرتبطة بقطعة y-combinator وبعض المواقع الأخرى ، وقد كان لدي مؤخرًا مستجد يسأل عنها ، لذلك أنا أشير إلى أنه في هذه المسألة والتغييرات اللاحقة ، اتصل بـ 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 التقييمات