Skip to content
Merged
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
208 changes: 208 additions & 0 deletions text/0000-دالة-مقاطعة-وبوابة-x86-intrcc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
- **عنوان المقترح:** مُعدِّل `دالة مقاطعة` (خفض `x86_intrcc`) + مدمج `حمل_سجل_المهمة` (ltr)
- **النطاق:** لغة `text/`
- **تاريخ البدء:** 2026-08-24
- **رقم الـ RFC:** (يُترك فارغاً حتى الدمج)
- **الحالة:** مقترَح
- **Issue التتبُّع:** (يُترك فارغاً حتى القبول)

# ملخّص

قدرتان متلازمتان تُطفئان أكعاب دخول المقاطعات في نواة النحلة:

1. **مُعدِّل `دالة مقاطعة`** — مُعدِّل يلي `دالة` قبل الاسم (نمط `دالة لا_ترجع`
المرسوم في RFC لا_ترجع) يجعل المصرِّف يخفض الدالّة باتفاقية النداء
`x86_intrcc` في LLVM، فيتولّد لها prologue/epilogue بوابةِ مقاطعةٍ كاملان:
حفظ سجلّات الأغراض العامّة المتطايرة، تسويةُ المكدّس لرمز الخطأ، والعودة
بـ`iretq`. لا حاجة لمدمج `ارجع_من_مقاطعة()` — العودة تأتي من الاتفاقية ذاتها.
2. **مدمج `حمل_سجل_المهمة`** (ltr) — نظير `حمل_جدول_مقاطعات` (lidt)
و`حمل_جدول_واصفات` (lgdt) القائمَين، يُحمِّل سجلَّ المهمّة (TR) بواصف TSS —
البوّابة اللازمة لمكدّسات IST التي تعتمد عليها معالجات المقاطعات الجادّة.

# الدافع (Motivation)

المتطلَّب المنبعيّ صريح في خارطة نحلة على موضعين:
- بند **م-لغة.3** (`توثيق/خارطة_المراحل.md:101`): «سمة x86_intrcc التي تنقل
أكعابَ دخول المقاطعات إلى لغة ص **+ خيار حفظ SSE (fxsave/fxrstor أو
-mgeneral-regs-only المكافئ)**».
- شرط قبول البند المستهلِك له في `roadmap.json` (feature-14): «سمة
`x86_intrcc` مسدودةٌ منبعيًّا في مستودع اللغة عبر RFC لا ملتفٌّ عليها».

المرحلة 3.6أ هناك منجزة — IDT يُبنى بلغة ص فعلًا — وأكعابُ الدخول (حفظ
السجلّات + `fxsave` + `iretq`) جزءٌ من `مقاطعات.S` لا يُطفأ إلا بهذا
المُعدِّل. (أرقام الأسطر تُقاس من عدّاد حارس ميزانيّة الأسمبلي في مستودع
نحلة لا تُنثر هنا — الملفّ يحوي أيضًا أغلفةً أخرى خارج نطاق هذا المقترح.)

وبدون المُعدِّل: معالجُ مقاطعةٍ مكتوبٌ بص يدخل ويعمل، لكنّه **لا يستطيع
الرجوع** — فلا مقاطعة مؤقّت، فلا جدولة، فلا نظام تشغيل؛ برامج خطّية فقط
(المقيس ب١٢٥ في بطارية مجسّات نحلة).

وبديل «مدمج `ارجع_من_مقاطعة()` يبثّ iretq» مرفوض هندسيًّا: `iretq` داخل جسم
دالّة عاديّة ذات prologue اعتياديّ (`push rbp`) تعود على إطارٍ مكسور وسجلّاتٍ
غير محفوظة — خطأ صامت لا يلتقطه اختبار. بوّابة المقاطعة **خاصيّة دالّةٍ لا
تعليمة**، ومكانها اتفاقية النداء.

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

```sad
دالة مقاطعة عالج_مقاطعة_المؤقت(رقم عنوان_الإطار)
زد_عداد_النبضات
نهاية

دالة هيئ_المقاطعات()
سجل_معالج_مقاطعة(32، عنوان_رمز("عالج_مقاطعة_المؤقت"))
حمل_سجل_المهمة(محدد_مقطع_حالة_المهمة) # ltr — بعد بناء TSS/IST بلغة ص
نهاية
```

- المعامل الأوّل إلزاميّ: إطار المقاطعة الذي يدفعه العتاد. سطحيًّا يُصرَّح
`رقم` ويُقرأ عنوانًا؛ **بنيويًّا يخفضه المصرِّف مؤشّرًا بسمة `byval`**
(انظر الشرح المرجعي — هذا شرط مُصادِق LLVM لا خيار).
- معامل ثانٍ اختياريّ `رقم`: رمز الخطأ (لاستثناءات العتاد التي تدفعه).
- العائد `فراغ` حصرًا. أيّ توقيع آخر خطأُ تصريفٍ من الكتالوج.
- الدالّة **لا تُنادى** — لا نداءً مباشرًا ولا عبر مرجع دالّة؛ تُؤخذ عنوانًا
حصرًا عبر `عنوان_رمز`. المخالفة خطأ دلاليّ مشترك المحرّكات.
- **جسم المعالج أغراضٌ عامّة فقط**: أيّ عمليّة عشريّة/متّجهيّة داخل
`دالة مقاطعة` خطأ تصريف (المرحلة الأولى — انظر «حالة SSE» أدناه).

# الشرح المرجعي (Reference-level explanation)

## الأنبوب — مرآة `لا_ترجع` (سبع محطّات)

| المحطّة | الموضع (نظير لا_ترجع) |
|---|---|
| كلمة سياقيّة في SoT | `language-truth/keywords.yaml` (نظير `KW-CTX-040` — تنبيه: هذا المعرّف مكرّر اليوم للسطرين 119 و171؛ الكلمة الجديدة تأخذ معرّفًا فريدًا ويُبلَّغ التصادم القائم) |
| التحليل بعد `دالة` قبل الاسم | `shared/parser/src/declarations/parser_declarations.cpp:65-77` |
| الضبط على `FunctionDecl` (٣ مواقع) | `parser_declarations.cpp:152, 437, 466` |
| حقل AST بولياني | `shared/ast/include/declarations.h` (نظير `isNoReturn`) |
| النقل إلى SIR | `compiler/src/frontend/sir_builder_functions.cpp` |
| حقل SIR | `compiler/include/frontend/sir_module.h` |
| الخفض | `compiler/src/backend/llvm/builders/oop/functions2_ops.cpp` |

## عقد الخفض (شروط مُصادِق LLVM — ليست اختيارات)

- `fn->setCallingConv(llvm::CallingConv::X86_INTR)` عند `Function::Create`
قبل أيّ منطق ABI (السابقة: `llvm_multiboot.cpp` يضبط `Win64` لدالّة مولَّدة).
- **المعامل الأوّل `ptr` بسمة `byval(بنية_الإطار)` وجوبًا** — المُصادِق يرفض
الوحدة بغيرها («Calling convention parameter requires byval»). بنية الإطار
الدنيا: خمس كلمات 64-بت (RIP، CS، RFLAGS، RSP، SS). التصريح السطحيّ `رقم`
يُجسَّر داخل المقدّمة بتحويل المؤشّر عددًا (`ptrtoint`) فيرى كاتبُ ص
عنوانًا خامًا كما اعتاد في مدمجات العتاد.
- رمز الخطأ (المعامل الثاني إن وُجد) `i64` على x86_64؛ الاتفاقية تتولّى
تسوية المكدّس قبل `iretq`.
- **`noredzone` على الدالّة وجوبًا**: الاتفاقية لا تضبطها، ومعالجٌ يعتمد
المنطقة الحمراء يُفسِده تداخل NMI. (يُقاس أوّلًا: إن كان مسار `--حر` يعطّل
المنطقة الحمراء وحدةً كاملةً فالسمة توثيقٌ تحصينيّ؛ وإلّا فهي السدّ.)
- علمٌ بولياني مطبوع عبر الأنبوب لا سلسلة نصّية — فلا يقع في فخّ «السمة
المجهولة تُقبل بلا أثر» القائم في مسار `[[سمة]]`.

## حالة SSE (المتطلَّب المنبعيّ الثاني)

`x86_intrcc` يحفظ سجلّات الأغراض العامّة، **ولا يحفظ حالة FPU/SSE ولا
MXCSR** — ونحلة تملك مسبارًا حيًّا (زرع xmm7 عبر المقاطعة) سيحمرّ لو أُفسدت.
قرار المرحلة الأولى: **مكافئ `-mgeneral-regs-only` تصريفيًّا** — حارسٌ يرفض
أيّ تعليمة عشريّة/متّجهيّة (ولو مولَّدةً ضمنًا) داخل جسم `دالة مقاطعة`
وكلِّ ما يُنادى منه في الوضع الحرّ يبقى على عاتق الكاتب (الجسم لا ينادي —
انظر مصفوفة التراكيب). خيارُ `fxsave/fxrstor` المولَّد (منطقة 512 بايت
بمحاذاة 16) يُرحَّل مُعدِّلَ وسيطٍ لاحقًا (`مقاطعة(احفظ_العائم)`) إن احتاجته
معالجات تلمس SSE.

## الحرّاس (كلّها أخطاء كتالوج، لا سقوط صامت)

| # | الحارس | الطبقة |
|---|---|---|
| 1 | التوقيع `(رقم[, رقم]) -> فراغ` | **الدلاليّة المشتركة** (`shared/semantic/`) — فتصل المحرّكات كلّها، لا الخفض وحده (درس توحيد SEM018) |
| 2 | منع النداء (مباشرًا وعبر مرجع دالّة/«أي») — العنوان عبر `عنوان_رمز` حصرًا | الدلاليّة المشتركة |
| 3 | لا عمليّات عشريّة/متّجهيّة في الجسم | الدلاليّة المشتركة |
| 4 | بوّابة المعماريّة: `x86_64` حصرًا في المرحلة الأولى (قرار نحلة «64-بت فقط»؛ i386 يختلف عقده — رمز خطأ i32 وعودة `iret` — فيُرجأ حتى يطلبه عميل) | الخفض (ثالوث الهدف) |
| 5 | بوّابة الوضع الحرّ: المُعدِّل يُرفض مستضافًا | الخفض |

## مصفوفة تراكيب المُعدِّلات

| التركيب | الحكم |
|---|---|
| `دالة لا_ترجع مقاطعة` | **تنافر تصريفيّ** — المقاطعة تعود بـ`iretq` وNoReturn يبثّ `unreachable` بعد النداء: دلالتان متعارضتان |
| `دالة خارجية` + مقاطعة | تنافر — المعالج يلزمه جسم |
| `متزامنة` / مولِّدة + مقاطعة | تنافر — لا حالة مُعلَّقة داخل بوّابة عتاد |

## مدمج `حمل_سجل_المهمة` (ltr)

على نمط `LOWLEVEL_GDT_LOAD` القائم (تسجيل في `builtins/compiler_os_core.yaml`
⇒ توليد ⇒ بناء SIR في `builtins_os_core.cpp` ⇒ خفض في `lowlevel_ops.cpp`)،
**لكن بقالبٍ مستقلّ لا بإعادة استعمال `emitFreestandingTableLoad`**: القيد
الموثَّق في `dialects/assembly_mnemonics/i686.yaml:167` — `ltr` تأخذ `r/m16`
فيرفضها المُجمِّع بعرض سجلٍّ كامل؛ يلزم القيد `${0:w}` ⇒ `ltr %ax` (الآليّة
قائمة ومقيسة في `statement_asm.cpp` واختبار `test_task_register_i686.py`).

**تسوية معجميّة لازمة**: لفظ اللهجة التجميعيّة `حمّل_سجل_المهمة` قائم في
`i686.yaml:167` وملاحظتُه تنصّ «لا مدمج قانونيّ يقابله» — والمصرِّف يجرّد
الشدّة فيتماهى الاسمان كتابيًّا. ضمن التنفيذ: تُحدَّث الملاحظة لتقول إنّ
المدمج القانونيّ صار قائمًا (الفضاءان معجميّان منفصلان — تعبيرُ لغةٍ مقابل
لفظِ تجميع — والتجانس مقصود لا تصادم).

## المفسّر والمحرّكات الأخرى

حرّاس التوقيع والنداء والجسم في الطبقة الدلاليّة المشتركة فيفرضها المفسّر
والمصرِّف سواء (لا ازدواج دلاليّ). أمّا **الأثر التنفيذيّ** (الاتفاقية) فلا
معنى له في المفسّر — قبول ضمنيّ بلا أثر، نمط `@تمثيل_سي` المرسوم. الخلفيّة
الأصليّة بلا LLVM: رفضُ بناءٍ صريح مبدئيًّا (دَين مُعلَن يُسدّ حين يبلغها
مسار الوضع الحرّ).

# السلبيات (Drawbacks)

- المُعدِّل أوّل بابٍ معماريّ الطابع في سطح اللغة (تخفّفه بوّابة المعماريّة
الصريحة، ولمعماريّات أخرى نظائر مستقبليّة تحت المُعدِّل نفسه).
- `X86_INTR` يحفظ كلّ سجلّات الأغراض العامّة المستعملة — أثقل من كعبٍ يدويٍّ
محسوبِ السجلّات؛ ثمنٌ مقبول مقابل الصحّة البنيويّة.
- حارس «لا عشريّ في الجسم» أضيق من الضرورة (يرفض معالجًا يريد fxsave يدويًّا)
— يُوسَّع لاحقًا بمُعدِّل الوسيط.

# أعمال سابقة (Prior art)

- **Rust**: سمة `x86-interrupt` غير المستقرّة (`#[naked]` بديلًا) — التجربة
هناك أثبتت أنّ البوّابة **خاصيّةُ دالّةٍ** لا تعليمةٌ في جسمها، وأنّ إلزام
المعامل الأوّل مؤشّرَ إطارٍ يمنع أكثر الأخطاء شيوعًا (إطارٌ مقروءٌ من مكدّسٍ
مزاح).
- **C/clang و GCC**: `__attribute__((interrupt))` على x86 — العقد نفسه
(`byval` للإطار، معاملٌ ثانٍ لرمز الخطأ، عودةٌ بـ`iretq`)، ومعه القيدُ
المعروف أنّ الاتفاقيّة **لا تحفظ حالة SSE/MXCSR** — وهو ما يبرّر حارسَ
«لا عشريّ في الجسم» في المرحلة الأولى بدل `fxsave` مولَّد.
- **داخل ص**: مُعدِّل `لا_ترجع` هو السابقة البنيويّة الحيّة — أنبوبُه السباعيّ
(كلمة سياقيّة في مصدر الحقيقة ⇒ تحليل ⇒ حقل AST ⇒ SIR ⇒ خفض) هو القالبُ
الذي يسير عليه هذا المُعدِّل حرفًا بحرف؛ و`حمل_جدول_مقاطعات`/`حمل_جدول_واصفات`
سابقةُ المدمج المرافق.

# البدائل (Alternatives)

- **مدمج `ارجع_من_مقاطعة()`**: مرفوض (إطار مكسور — انظر الدافع).
- **سمة `[[معالج_مقاطعة]]`**: أنبوبها أقصر (ملفّان)، لكنّها نصٌّ خام يقبل
الخطأ الإملائيّ بصمت، والمتطلَّب المنبعيّ صيغ مُعدِّلًا («دالة مقاطعة»).
- **fxsave المولَّد من المرحلة الأولى**: يؤجَّل — يُدخل 512 بايت لكلّ إطار
معالجٍ وأغلبُ المعالجات لا تلمس العائم؛ الحارس التصريفيّ يحفظ الصحّة
بثمن صفر.
- **إبقاء الأكعاب في `مقاطعات.S`**: يخالف شرط «مسدودة منبعيًّا لا ملتفّ
عليها» المُعلن في شرط قبول feature-14.

# أسئلة غير محسومة (Unresolved questions)

- **الاسم**: `دالة مقاطعة` (طلب نحلة الحرفيّ؛ تُقرأ إضافةً «دالّةُ مقاطعةٍ»)
أم `دالة معالجة_مقاطعة` (صفة مؤنّثة تطابق قاعدة «الموصوف ثمّ الصفة»)؟
التوصية الأوليّة: `مقاطعة` — أقصر، وطلبها العميل نصًّا، وقراءتها الإضافيّة
سليمة نحوًا.
- تمثيل بنية الإطار في سطح اللغة لاحقًا: هل تُعرَّف بنية `@تمثيل_سي` قياسيّة
(`إطار_مقاطعة`) بدل `رقم` الخام؟ (الخفض `byval` قائم في الحالين؛ الفرق
سطحيّ.)
- قياس تعطيل المنطقة الحمراء في مسار `--حر` (يحسم هل `noredzone` سدّ أم
توثيق — انظر عقد الخفض).

# أعمال مستقبلية (Future possibilities)

- **مُعدِّل وسيط `مقاطعة(احفظ_العائم)`**: يولّد `fxsave/fxrstor` على منطقة
512 بايت بمحاذاة 16 لمعالجاتٍ تلمس العائم — يرفعُ حارسَ «لا عشريّ» عن
الدوالّ التي تطلبه صراحةً بثمنٍ معلَن.
- **بنية `إطار_مقاطعة` قياسيّة** بـ`@تمثيل_سي` بدل `رقم` الخام، فيقرأ الكاتبُ
`RIP`/`RFLAGS` بأسمائها لا بإزاحاتٍ محسوبةٍ يدويًّا.
- **نظائر معماريّة**: `aarch64` (بوّابةُ استثناءٍ عبر `VBAR_EL1`) و`riscv`
(`mtvec`) تحت المُعدِّل نفسِه — البوّابةُ الصريحةُ في هذا المقترح تجعل
التوسيعَ إضافةَ فرعٍ لا تغييرَ سطحٍ.
- **`دالة مقاطعة` في الخلفيّة الأصليّة بلا LLVM**: توليدُ الكعبِ يدويًّا
(حفظُ السجلّاتِ ثمّ `iretq`) حين يبلغُها مسارُ الوضعِ الحرّ.
Loading