Skip to content

التعاملُ مع C — تمثيلُ البنيةِ والدوالُّ الخارجيّة

نواةٌ لا تخلو من C: مُحمِّلُ الإقلاعِ يناديك، وحزمةُ دعمِ اللوحةِ تُصدِّرُ لك رموزًا، وأنت تُصدِّرُ لها. هذه الصفحةُ في اتّجاهَيْ ذلك الجسرِ الثلاثة: تستدعي C · تُصدَّرُ إلى C · تُشارِكُ تخطيطَ بنية.

١ · أن تستدعيَ رمزَ C: دالة خارجية

تصريحٌ بلا جسمٍ ولا «نهاية» — التوقيعُ وحدَه، يُربَطُ وقتَ الوصل:

sad
دالة خارجية("kmalloc") رقم خصص_نواة(رقم)
llvm
declare i64 @kmalloc(i64)

والصيغُ الثلاثُ المُعلَنةُ في gr.decl.extern:

sad
دالة خارجية("cos") عشري جيب_التمام(عشري)      # اسمٌ عربيٌّ ورمزٌ مغاير
دالة خارجية رقم مدخل()                        # بلا معاملات
دالة خارجية printf(نص)                       # الاسمُ هو الرمز

فخٌّ مقيسٌ: لا تُصرِّحْ printf حرًّا

السطرُ الثالثُ يُترجَمُ مستضافًا (رمزُ خروجٍ صفر) ويُسقِطُ المترجّمَ حرًّا. المقيس — ملفٌّ لا يحوي غيرَ ذلك التصريحِ ودالّةٍ فارغة:

bash
$ sad-build a.ص --حرّ --بلا-رئيسية \
      --هدف=x86_64-unknown-none-elf --أظهر-llvm -o a.ll
Module verification failed:
Incorrect number of arguments passed to called function!
  call void @printf(ptr @sad.panic.fmt, i64 %0)

error: [INT011] خطأ مترجم داخلي: وحدة LLVM المولَّدة غير سليمة (verifyModule)

السببُ ظاهرٌ في نصِّ العطبِ نفسِه: مسارُ الهلعِ الحرَّ يبثُّ @printf بتوقيعِه المتغيّر، فيصطدمُ تصريحُك بتوقيعٍ ثابت. وهذا عطبُ مترجّمٍ لا قاعدةُ لغة — والعلاجُ العمليُّ أن تُسمِّيَ رمزَك غيرَ printf (دالة خارجية("kprintf") …)، أو ألّا تصرّحَ به حرًّا أصلًا فلا stdio هناك على أيّةِ حال.

«خارجية» صفةٌ لا كلمةٌ مفتاحيّةٌ سابقة

الترتيبُ دالة خارجية — الصفةُ بعدَ الاسمِ وتطابقُه (RFC 0034). والصيغةُ القديمةُ خارجي دالة أُزيلت، ويقابلُها حاجزُ خطأٍ توجيهيّ.

٢ · أن تُصدَّرَ إلى C: @رمز على الدالّة

الاسمُ العربيُّ يُشوَّهُ داخليًّا، فلا يجدُه رابطٌ ولا مُجمِّع. @رمز تُثبِّتُ له اسمَ ASCII بربطٍ خارجيّ:

sad
@رمز("serial_putc") دالة أرسل_حرفًا(رقم حرف)
    اكتب_منفذ(0x3F8، حرف)
نهاية
llvm
define void @serial_putc(i64 %"حرف") {

define لا declare — أي جسمٌ حقيقيّ — وربطٌ خارجيٌّ لا internal.

وهذا هو المسارُ العمليُّ لتجاوزِ تعريفٍ في زمنِ التشغيل. الوضعُ الحرُّ يبثُّ __sad_panic تعريفًا ضعيفًا (weak_odr) و__sad_serial_putc كذلك — وقد رُصِدا في IR كلِّ وحدةٍ حرّةٍ تُرجِمت لهذه الصفحة. فتعريفُك بـ@رمز("__sad_panic") يغلبُهما.

ركِّبِ المُعدِّلَ في موضعِه

@رمز + لا_ترجع معًا تعطي رمزًا مُصدَّرًا بسمةِ noreturn. والترتيبُ دالة لا_ترجع اسم() — المُعدِّلُ يلي «دالة» قبلَ الاسم. وقد قِيسَ أنّ الصيغةَ اللاحقةَ دالة اسم() لا_ترجع تُترجَمُ بلا شكوى ولا تُنتِجُ noreturn؛ صيغةُ التصريحِ الأولى تعطي ; Function Attrs: noreturn فوقَ define، والثانيةُ لا شيء.

٣ · تخطيطُ البنية: @تمثيل_سي

سمةٌ اسميّةٌ بلا وسيطٍ تسبقُ «بنية»، نظيرُ #[repr(C)]:

sad
@تمثيل_سي
بنية وقت_س
    رقم ثوانٍ = 0
    رقم نانو = 0
نهاية

تُترجَمُ، ويُسجَّلُ النوعُ بلا ترويسةِ صنفٍ خفيّة:

llvm
%"class.نقطة" = type { i64, i64 }
%"class.متجه" = type { double, double }

وتُرفَضُ على غيرِ البنيةِ نحويًّا (مرسًى سالبٌ في rules_matrix/30_oop/gr.oop.struct/negative/053_c_repr_on_non_struct.ص).

ما لم أستطعْ إثباتَه — وهو الأهمُّ في هذه الصفحة

مصدرُ الحقيقةِ يُعلِنُ التوجيهَ status: experimental، ونصُّه يقول: «تُحلَّل وتُقبَل بلا أثرٍ تخطيطيٍّ بعد» (RFC #53، الحاجز ٣/F2-أ). وفي المترجّمِ دالّةُ تصنيفٍ حقيقيّةٌ تميّزُ SysV من Win64:

compiler/src/backend/llvm/builders/oop/classes_vtables_ops.cpp:1142
    CReprAbiInfo ClassesVtablesCodeGen::classifyCReprAbi(...)

منطقُها مطابقٌ لِما يولّدُه Clang: Win64 ⇒ ١/٢/٤/٨ بايتاتٍ في سجلٍّ صحيحٍ واحدٍ وما عداه بمؤشّرٍ خفيّ؛ SysV ⇒ ما فوقَ ١٦ بايتًا ذاكرةٌ، وإلّا ثمانيّةٌ أو ثمانيّتانِ تُصنَّفُ كلٌّ INTEGER أو SSE.

لكنّي لم أستطعْ رصدَ أثرِها من سطحِ اللغة. جرَّبتُ ثلاثةَ أشكال — بنيةً من ١٦ بايتًا صحيحةَ الحقول، وبنيةَ عشري مزدوجةً (التي وحدَها تكشفُ تصنيفَ SSE)، وتمريرَها بالقيمةِ إلى دالة خارجية — على الثالوثَينِ x86_64-unknown-none-elf وx86_64-pc-windows-msvc، فكان الناتجُ في الستِّ جميعًا واحدًا:

llvm
declare double @vec_len(i64)
%calltmp = call double @vec_len(i64 %"%ف.load")

أي مؤشّرٌ في i64، لا قطعُ سجلّاتٍ ولا sret، ولا فرقَ بين الثالوثَين. فلا تبنِ اليومَ ربطًا يعتمدُ على تمريرِ بنيةٍ بالقيمة إلى C.

البديلُ المقيس

مرِّرْ عنوانًا لا بنية: عنوان_رمز لأخذِ العنوان، واقرأ_ذاكرة* و اكتب_ذاكرة* لقراءةِ الحقولِ وكتابتِها بإزاحاتٍ بايتيّة. وهو نمطٌ مقيسٌ عاملٌ — انظر MMIO والمنافذ.

٤ · وصفةُ الجسرِ في الاتّجاهَين

sad
@رمز("kernel_ticks") متغير متطاير نبضات = 0

دالة خارجية("kmalloc") رقم خصص_نواة(رقم)

@رمز("kmain") دالة لا_ترجع نواة()
    متغير ذ = خصص_نواة(4096)
    اكتب_ذاكرة64(ذ، 0)
    نبضات = نبضات + 1
    بينما (صحيح)
        أسبت_المعالج()
    نهاية
نهاية

ما يراه الرابط: kernel_ticks بيانًا مُصدَّرًا · kmain دالّةً مُصدَّرةً noreturn · kmalloc مرجعًا غيرَ محلول تُوفّرُه أنت.

٥ · قواعدُ لا تُخالَف

القاعدةلماذا
اسمُ @رمز ASCIILLVM يهرّبُ غيرَ ASCII في المعرّفات، ورموزُ النواةِ ASCII
لا نهاية بعدَ دالة خارجيةتصريحٌ بلا جسم — إضافتُها خطأٌ نحويّ
لا مدمَجةَ مستضافةً في الجسرالبوّابةُ SEM019لماذا
العنوانُ لا البنيةُ في التمريرمقيسٌ أعلاه: البنيةُ لا تُصنَّفُ من سطحِ اللغة

مُرخَّص بموجب رخصة MIT