Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions text/0042-تصدير-الدوالّ-باسم-رمز.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
- **عنوان المقترح:** تصدير الدوالّ باسم رمز — `@رمز("اسم")` على تصريح دالّة
- **النطاق:** لغة `text/`
- **تاريخ البدء:** 2026-07-21
- **رقم الـ RFC:** 0042
- **الحالة:** مقترَح
- **Issue التتبُّع:** (يُترك فارغاً حتى القبول)

# ملخّص

نمدّ التوجيه القائم `@رمز("اسم")` — الذي يُصدِّر **متغيّرًا/ثابتًا** تحت اسم
رمز ASCII محدَّد (اللبنتان 3.14/3.16) — ليقبل **تصريح دالّة** كذلك. فتصير
`@رمز("bee_x") دالة اسم(...)` تُصدِّر الدالّة في LLVM باسم `bee_x` بربط
خارجيّ، بدل اسمها الداخليّ المُشوَّه — فيستدعيها كود C أو الكود الذي يولّده
المترجم بالاسم حرفيًّا.

المبدأ الحاكم نفسه: **الرمز عقد ABI مع العالم الخارجيّ، والاسم العربيّ يبقى
لغة ص.**

# الدافع (Motivation)

- **إطفاء آخر تعريفات C في نواة النحلة:** المترجم يولّد نداءات إلى رموز
ABI محجوزة (`__sad_panic` لمسارات حدود المصفوفات، `__stack_chk_fail`
لفشل حارس الإطار). اليوم تُعرَّف هذه في C (`تراب.c`) لأنّ ص لا تملك
تصدير دالّة تحت اسم ASCII — فيتعذّر بلوغ هدف «صفر C». هذا التوجيه يزيل
العائق: تُعرَّف الدالّة بلغة ص وتُصدَّر تحت الاسم المحجوز.
- **تناظر مع `@رمز` على البيانات:** تصدير البيانات تحت اسم رمز (3.14)
وتصدير الكود تحته وجهان لعقد ABI واحد؛ إبقاء أحدهما دون الآخر تفاوت
تعسّفيّ.
- **الاستضافة الذاتيّة والـFFI العكسيّ:** أيّ سيناريو يستدعي فيه كود
خارجيّ (نظام تشغيل، مكتبة C، مُحمِّل) دالّةً مكتوبة بلغة ص يحتاج اسم
رمز مستقرًّا معلومًا.

# الشرح التوجيهي (Guide-level explanation)

`@رمز("اسم")` سمة سابقة على تصريح دالّة. تجعل رمز الرابط للدالّة = `"اسم"`
حرفيًّا (بدل الاسم الداخليّ)، بربط خارجيّ:

```sad
# تُصدَّر في LLVM كـ: define void @__sad_panic(i64 %الرمز) { … }
@رمز("__sad_panic") دالة لا_ترجع اهلع_المترجم(رقم الرمز)
اطبع_خام("هلع")
قف_أبديًّا()
نهاية
```

- الدالّة **معرَّفة كاملةً** (لها جسم) لا مجرّد إعلان — بخلاف `دالة خارجية`
التي تستورد رمزًا بلا جسم.
- تتركّب مع المعدِّلات القائمة: `@رمز("s") دالة لا_ترجع اسم()` ⇒ رمز مُصدَّر
بسمة `noreturn` (3.15).
- الاسم العربيّ (`اهلع_المترجم`) يبقى للاستدعاء من كود ص؛ الاسم ASCII
للعالم الخارجيّ. (اليوم إن اختُلف الاسمان يُصدَّر بالاسم ASCII فقط؛
الاستدعاء الداخليّ بلغة ص يمرّ عبر رمز الرابط نفسه.)

# التصميم التفصيليّ (Reference-level explanation)

الآليّة قائمة بالكامل تحت السطح؛ ينقصها ربط المحلّل فقط:

1. **AST:** `FunctionDecl` يملك أصلًا `linkName` (كان لاستيراد FFI عبر
`دالة خارجية("اسم")`). نعيد استعماله للتصدير — دلالته الموحَّدة «اسم
رمز الرابط لهذه الدالّة».
2. **المحلّل** (`parser_main.cpp`، كتلة `@رمز`): بعد تحليل التصريح التالي،
إن كان `FunctionDecl` يُضبَط `linkName = اسم_الرمز` (كما يُضبَط
`linkSymbol` على `VarDeclStmt`). رسالة الخطأ تُحدَّث لتشمل «دالة».
3. **جسر SIR:** `funcDecl->linkName → sirFunction->linkName` يتدفّق أصلًا.
4. **الخلفيّة:** اسم رمز LLVM = `sirFunc->getLinkName()` (=‏`linkName`
إن وُجد) أصلًا؛ والدالّة ذات `linkName ≠ name` تبقى `ExternalLinkage`
(لا تُدوَّل). فالتصدير يعمل بلا تغيير خلفيّ.

5. **حارس التصادم (SEM024):** إبقاء رمزين مُصدَّرين باسم رابط واحد على
تعريفين **مُعرَّفين** يجعل الخلفيّة تُفسدهما صامتًا: دالّتان تُدمَجان (جسم
الثانية يُلحَق ككتلة بلا سلف ويُفقَد)، ودالّة تشارك متغيّرًا الاسمَ تُعيد
LLVM تسمية أحدهما (`@اسم.1`) فيَضيع عقد الـABI — كلاهما كودٌ خاطئ صامت. لذا
يتتبّع الباني أسماء الرموز المُصدَّرة (مجموعتان: دالّة/متغيّر، متقاطعتان
مستقلًّا عن ترتيب المعالجة) ويرفض التكرار بخطأ قاتل `SEM024` لحالتَي
دالّة/دالّة ودالّة/متغيّر. (تصادم متغيّر/متغيّر يبقى لحارسه القائم خلفيًّا؛
والدوالّ الخارجيّة `دالة خارجية` بلا جسم = استيراد، فلا تتصادم فتُستثنى.)
المجموعتان تُمسحان عند دخول كلّ وحدة (لئلّا يُطلَق `SEM024` كاذبًا عبر
الوحدات في بانٍ مُعاد استخدامه — LSP).

**التغيير الصافي:** فرع `else if` واحد في المحلّل + حارس تصادم دلاليّ
(`SEM024`، تتبّع أسماء الرموز المُصدَّرة) + رسالة الخطأ. لا كلمة مفتاحيّة
جديدة، لا تغيير خلفيّ.

## القيود (الإصدار الأوّل)

- الدالّة المُصدَّرة يجب أن تكون بتوقيع بسيط قابل للتعبير في ABI الهدف
(لا أنواع عامّة/قوالب، لا async/مولّد — تلك تُشوّه أسماءها أو تحتاج بيئة
تشغيل). الاستعمال الأوّل (نواة، FFI عكسيّ) لا يحتاج غير التوقيع البسيط.
- مسؤوليّة تطابق التوقيع مع عقد المستدعي الخارجيّ على المبرمج (نظير
`دالة خارجية`).

# البدائل (Alternatives)

- **حقل `exportName` مستقلّ عن `linkName`:** أوضح دلاليًّا لكن يضاعف مسارات
الخلفيّة بلا كسب (كلاهما «اسم رمز الرابط»). رُفض لصالح إعادة الاستعمال.
- **كلمة مفتاحيّة `صدّر_كـ("اسم")`:** تركيب جديد بلا داعٍ؛ `@رمز` قائم
ومفهوم ومتناظر مع البيانات.
- **إبقاء `__sad_panic` في C أبدًا:** يناقض «صفر C» ويترك دَينًا دائمًا.

# أسئلة مفتوحة (Unresolved questions)

- تصدير دالّة عامّة/مُنمَّطة (بأيّ تخصيص؟) — مؤجَّل حتّى يظهر طلب فعليّ.
- حارس دلاليّ يرفض `@رمز` على async/مولّد/عامّة برسالة صريحة — تحسين
لاحق (الإصدار الأوّل يعتمد على بساطة الاستعمال؛ رصدته المراجعة العدائيّة
كفخّ بلا تشخيص M3). أمّا **تصادم اسم الرمز المكرَّر فمحروس منذ الإصدار
الأوّل** (`SEM024`) — دالّة/دالّة ودالّة/متغيّر معًا — بعد أن كشفته المراجعة
العدائيّة (دمج/إعادة تسمية صامتة ⇒ كودٌ خاطئ).
- تصدير `@رمز("malloc"/"free"/"memset"/…)` يتجاوز رمزًا يوفّره زمن التشغيل
(weak): مسار الخلفيّة يفرض نوع العائد لهذه الأسماء المعروفة، فقد يُبدَّل
عائد الدالّة المُصدَّرة صامتًا (رصد المراجعة العدائيّة M2). مقبولٌ في الإصدار
الأوّل (نظير `دالة خارجية`: تطابق التوقيع عهدةٌ على المبرمج)؛ حارس أسماء
محجوزة للدوالّ تحسينٌ لاحق.
- تجاوز مدمج بتوقيع مخالف (`@رمز("__sad_panic") دالة بلا معاملات`) يبقى
مسؤوليّة المبرمج (نظير `دالة خارجية`) — تطابق التوقيع مع عقد المستدعي
عهدةٌ عليه.

# الأثر على مصدر الحقيقة

لا شيء: `@رمز` توجيه سمة لا كلمة مفتاحيّة، فلا إدخال في `keywords.yaml` ولا
توليد. اختبارات منع الانحدار في
`tests/system/lowlevel_freestanding/test_symbol_export_func.py`.
Loading