لغة الـC كلغة تمثيلٍ وسيط IR

نشره م. وائل حسن -أ… في

عند بناء مُترجِمات لغات البرمجة programming languages compilers فإن جزءاً من أصعب الأجزاء التي يحتاج المبرمجون إلي بنائها هو النهاية الخلفية back end، المُترجِم في الأساس يتكون من ثلاثة أجزاءٍ رئيسةٍ هي:

* النهاية الأمامية front end.

* النهاية الوسطي middle end.

* النهاية الخلفية back end.
تكوين المُترجِم (من موسوعة ويكيبيديا)

و يقوم جزء الواجهة الأمامية بقراءة الكود الذي كتبه المُبرمِج و يكتشف الأخطاء التي فيه إن وُجِدت، فإن لم يكن هناك أخطاءٌ قام بتحويل ذلك الكود إلي ما يُسمَّي بشجرة السَّبْر الصافية abstract syntax tree، و التي هي عبارة عن مجموعةٍ من العُقَد nodes المتصلة ببعضها البعض و تُعبر عن سير البرنامج كما يريده المبرمج.
أما جزء الواجهة الوُسطَي  فيقوم بتحويل تلك الشجرة إلي لغةٍ وسيطة، لا تكون عالية المُستوَي كاللغة التي استخدمها المبرمج في الأصل، و لا تكون مُنخفضة المُستوي مثل لغات التجميع assembly languages. بل تكون وسطاً بين النوعين، و يمكن أن نصفها بأنها عبارة عن لغة تجميعٍ محمولة portable assembly language.
و في الختام يقوم جزء النهاية الخلفية بتحويل التمثيل الوسيط إلي كود تجميع خاصٍ بالمُعالِج processor المقصود. و من ثم يقوم ما يُسمَّي بالمُجمِّع assembler بتحويل كود التجميع إلي الأصفار و الآحاد و التي هي اللغة الحقيقية للآلات (هناك عملياتٌ أخري هنا و هناك، و لكني أحاول أن أُعطي فكرةً عامةً عن الأمر).

بناء المُترجِمات الاحترافية أمرٌ صعبٌ جداً و طويل الأمد للغاية، و تزيد صعوبته كلما زادت إمكانيات اللغة التي يتم بناء مُترجمِ لها. و لكن مِن أكثر الأجزاء صعوبةً و ثقلاً علي النفس: جزء الواجهة الخلفية؛ لأنه يُعتبر عملياً من أكثر الاجزاء  تعقيداً و ضخامةً و "حاجةً للتمديد مع الزمن".
أما الضخامة و الصعوبة فشرحهما له مقامٌ آخر، و أما الحاجة للتمديد مع الوقت فتأتي كنتيجةٍ طبعيةٍ لمحاولتنا دعم أرضياتٍ جديدةٍ يمكن للمُترجِم أن يُنتِج لها برامج تعمل عليها، ففي البداية ستدعم الأرضيات الأكثر شهرةً مثل معماريتَيْ X86 و ARM مثلاً، ثم بعد ذلك ستجد أنك تحتاج لدعم PowerPC و غيره من المُعالِجات، و هكذا تكتشف أن أجزاء النهاية الخلفية صارت تتكاثر كالأرانب البرية !.
و لهذا كله فما إن يري مبرمجو المُترجِمات أي فرصةٍ للتخلص من عبء بناء الواجهة الخلفية حتي يهرعوا إليها خِفافاً و ثِقالا !


و من هذه الفرص التي يُمكن أن تريح هؤلاء المبرمجين: إمكانية استخدام لغة برمجةٍ متوسطة المُستوي موجودةٍ بالفعل كلغة تمثيلٍ وسيط، و يمكن الاستفادة من هذا في الأمور التالية:

* عدم الحاجة لبذل الجهد الشديد في بناء النهاية الخلفية من الصفر، و قد شرحنا العوامل التي تجعل هذا المجهود خرافياً. و بعد توفيره يمكن توجيهه إلي جزءٍ آخر من المشروع.

* حينما تكون اللغة متوسطة المستوي لها مُترجِماتٌ يمكنها ترجمة الأكواد المكتوبة بها إلي كثيرٍ من المنصات المختلفة: فإن لغتنا الجديدة سيكون لها ذات الإمكانية. و هذا هامٌ بما لا يُقاس؛ تخيل أن تقوم بضربةٍ واحدةٍ بإعطاء لغتك إمكانية إنتاج برامجٍ تعمل علي عشرات المُعالِجات و أنظمة التشغيل المُختلِفة ! سيحقق هذا بلا شك طفرةً كبيرةً جداً في محمولية portability لغتك الجديدة. كما أنه حينما يكون هناك تحسيناتٌ مقبولةٌ في المُترجم الذي ستستعمله كنهايةٍ خلفيةٍ فسيجعلك هذا تضيف "كفاءة استغلال البرامج المكتوبة بلغتك لموارد الحاسوب" إلي قائمة المكاسب التي حصلتَ عليها بضربةٍ واحدة.

* مهما كانت اللغة منخفضةً في مستواها فإنها بلا شكٍ ستكون أفضل عند برمجة البرامج الضخمة من أكواد التجميع assembly code؛ فهي علي الأقل ستُوفِّر لك مكوناتٍ أكثر قرباً للبرمجة عالية المستوي من تلك الأخيرة، و بهذا يُمكنك الانتهاء من بناء النهاية الوسطي ذاتها في وقتٍ أقل مما كان سيكون عليه في حالة الاعتماد علي لغةٍ وسيطةٍ أقرب للغة التجميع.

* نظراً  لأن المجهود المبذول في تغيير قواعد لغتك الجديدة و معانيها سيكون أقل بالمقارنة مع الحالة التي تستخدم فيها أكواد التجميع بدلاً من اللغة متوسطة المستوي: فإنك سنكون أقدر علي تطوير لغتنك و أكثر جرأة. في الواقع أنا أكره التغييرات الجوهرية في لغات البرمجة و لا أطيق حدوثها، لكن هناك بعض الأحيان التي تحتاج فيها للتغير الكبير و/أو المتعدد في لغة البرمجة الخاصة بك، مثلاً لو كنتَ تعمل في بحثٍ تجريبيٍ و تريد أن تقوم بتجربة أكثر من شكلٍ من أشكال القواعد في لغتك لتختار الأنسب، من حيث التناسق مع بقية القواعد أو من حيث الأداء الأفضل أو غيرهن من عوامل المقارنة، أو أن تكون أستاذاً جامعياً يبني مُترجِماً يصلح أن يستخدمه طلابه لبناء العديد من لغات البرمجة استناداً إلي أكواده. في هاتين الحالتين و أمثالهن ستحتاج إلي مرونةٍ عاليةٍ في بناء المُترجِم، و قابلةٍ عاليةٍ لتسريع عملية التغيير في القواعد، و هو ما توفره هذه المنهجية.


و لكن يجب أن تكون اللغة التي سنستخدمها كلغة تمثيلٍ وسيطٍ  ينطبق عليها الشروط التالية؛ حتي يصير استعمالها مقبولاً:

* أقل في المستوي من لغتك التي تبني لها مترجماً جديدا؛ لأنها لو كانت ذات مستويً مُقارِبٍ لها لأصبح الأمر مجرد ترجمة برنامجٍ من لغةٍ برمجةٍ إلي لغة برمجةٍ أخري، فتصير اللغة الجديدة مجرد واجهةٍ للغة القديمة.

* ذات مستوي برمجةٍ منخفض بما يكفي للتعامل مع العتاد و استخدامها لذلك الغرض بحريةٍ و مرونة.

* ذات محموليةٍ عالية؛ أي أن تكون البرامج المكتوبة باستخدام تلك اللغة الوسيطة قابلةً للترجمة علي العديد من المُعالِجات، و إعطاء نفس النواتج مهما اختلفت الأرضية التي تعمل عليها.

* ذات توحيدٍ قياسيٍ عالٍ، أي أن قواعد تلك اللغة تكون مكتوبةً بشكلٍ واضحٍ بطريقةٍ يمكنك الاعتماد عليها، و حينما تكتب برنامجاً باستخدامها يعمل كما هو مشروحٌ في المواصفات القياسية للغة.

* غير قابلةٍ لتدمير المفاهيم المنطقية للبرامج المُترجَمة إليها، أي أنه حينما تكتب برنامجاً باللغة الوسيطة لا يقوم المُترجِم علي سبيل المثال بالتغيير فيه بأي شكلٍ من الأشكال التي تضر بمنطقيات لغتك و ما يُفترَض أن يحدث في الأصل. و لو كانت قادرةً علي فعل هذا فيجب أن يكون هناك طريقةٌ لتجنبه في المُترجِم.

* وجود مُترجِماتٍ لها إلي كثيرٍ من المعالجات المشهورة، فمن ضمن الأغراض الأساسية لهذه المنهجية: الاستفادة من ميزات اللغة منخفضة المستوي كلها و من أهمها قابلية برامجها للعمل علي أكثر من منصةٍ platform مشهورة.

* أن تكون تلك المُترجِمات القوية مفتوحة المصدر open source، في الواقع يمكنك استخدام المُترجمات مغلقة المصدر كما تحب، و لكن استخدام النوع الذي تتوافر لديك أكواده أفضل بكثير؛ فربما تحتاج لمعرفة كيفية بناء المُترجم الذي تستعمله للأكواد، حينها لا تستطيع فعل ذلك مع المُترجمات مغلقة المصدر بينما يمكنك أن تفعل هذا مع الحرة. بل ربما تحتاج في بعض الأحيان لتعديل طريقة عمل المترجم بما يتوافق مع مشروعك الخاص تعديلاً كبيراً أو غير كبير، و هذا سيجعلك لا محالة تستخدم المترجمات الحرة.


لكن من العيوب التي أراها في هذه المنهجية ما يلي:

* تَحرِم المبرمج الذي يستخدمها من خبراتٍ كثيرةٍ كان سيحصل عليها من سلوك المنهج الأصلي (أعني بناء الواجهة الخلفية التي تُنتِج أكواد التجميع بنفسه). و هذا بالطبع أمرٌ يعتمد علي الخبرة التي يمتلكها ذلك المبرمج من الأصل؛ فلو كان صاحب خبرةٍ طويلةٍ في بناء المترجمات فلن يكون لهذا العيب وجود، أما لو كان مثلي قليل الخبرة فسيحتاج إلي شجاعة الأعتراف بأنه "لا مفر يا صاح، عليك وضع كلتا يدَيْك في صندوق التروس !". و كذا يعتمد الأمر علي الوقت المُتاح لإنجاز المشروع، و الدعم المادي المُتوافر له.

* سوء انتقاء اللغة التي تعمل كلغةٍ وسيطةٍ  و/أو مُترجمها الذي سنستعمله كنهايةٍ خلفيةٍ ربما يؤدي إلي نقصٍ في المحمولية و/أو ضعف أداءٍ للبرامج المُنتَجة و/أو سوء استغلالها لموارد الحاسوب، أو حتي تغير المفاهيم المنطقية للبرنامج !

* البطء في الترجمة لو كان مترجم اللغة الوسيطة بطيئاً، أو ظهور عللٍ في البرامج المُنتَجة إذا كان ذلك المُترجِم تظهر فيه عللٌ أثناء عمله. خذوا مثلاً تجميعة مُترجِمات الـgcc التي لم يدعمها فريق نظام الـ plan9 حتي فترةٍ قريبة (لا أعلم هل دعموه حتي الآن أم لا)، بل و قام أحد أعضاء الفريق بمهاجمتها بشدة أثناء أحد العروض التقديمية له عن plan9 في أحد محاضرات مؤتمر الـfosdem في عام 2006 بسبب العِلل التي تظهر فيه بعض الأحيان في ظروف تحسينٍ optimizaion مُعيَّنةٍ ! (أُنبِّه إلي أنه لا شأن لي بآرائهم في الـgcc؛ فلا أريد أن يسبني أحدهم بسبب هذا).



حسنٌ: هل تصلح الـC كلغة تمثيلٍ وسيط ؟:
أظن أن الـC من أفضل اللغات التي تنطبق عليها شروط اللغات الوسيطة السالفة الشرح؛ فلو حاولنا تطبيق الشروط التي وضعناها بالأعلي عليها فسنري أنها تنجح بامتياز:

* الـC لغةٌ منخفضة المستوي جداً بحيث صار من الواضح أنها ليست إلا لغة تجميعٍ محمولة، مهلاً: قبل أن يبدأ أحدكم في التشنيع عليَّ أو مطالبتي بالتعلم قبل التكلم أريدكم أن تقرؤوا ما قاله لينوس تورفالدز linus torvalds عن هذا الأمر في إحدي تدويناته القصيرة (علي مدونته الشخصية التي هجرها منذ أعوام)، حيث كتب:
    Some people seem to think that C is a real programming language, but they are sadly mistaken. It really is about writing almost-portable assembly language, and it turns out that getting good results from SHA1 really is mostly about trying to fight the compilers tendency to try to be clever.

    و أُترجمها:
    يبدو أن بعض الناس يفكرون في الـC علي أنها لغة برمجةٍ حقيقية، لكنهم مع الأسف مخطئون؛ هي في الحقيقة ليست إلا لغة تجميعٍ محمولة (أي: للعمل علي أكثر من مُعالِج)، و اتضح في النهاية أن الحصول علي نتائجٍ جيدةٍ من خوارزمية SHA1 يعتمد في معظمه علي محاربة محاولات المُترجِم أن يكون ماهراً !

    و لينوس يقصد بـ"محاولات المُترجِم أن يكون ماهراً" أن يقوم المترجم بالتغيير في الأكواد التي يكتبها المبرمج للوصول إلي استغلالٍ أفضل للمعالج أو الذاكرة، و هو ما ينتج عنه أخطاءٌ في عمل البرنامج لو كان يعتمد علي الأوامر الأصلية دون غيرها و بذات الترتيب الذي كُتِبت به دون غيره، و هو ما لا يحدث إلا مع لغات التجميع، و لذا وصف لينوس لغة الـC بأنها ليست لغة برمجةٍ حقيقيةٍ بل لغة تجميعٍ محمولة.

* الـC ذات محموليةٍ عالية، صحيحٌ أنك ستواجه مشاكلاً مع اختلاف الإصدارات و اختلاف "رؤية" بناة المترجمات لكثيرٍ من الأمور، إلا أنك في النهاية ستستخدم مترجماً يخضع لرؤيةٍ واحدة (يمكنكم تسميتها: التوحيد القياسي للمُترجِم)، لذا فليست هناك مشكلة في هذه النقطة أو في نقطة التوحيد القياسي العالي.

* يمكن لمترجمات الـC تدمير المفاهيم المنطقية للبرامج المُترجمة إليها عند استخدامها كنهايةٍ خلفية؛ و ذلك كما أوضحنا من خلال كلام لينوس السابق الذِكر بسبب محاولات مُترجِم الـC أن يكون ماهراً و يقوم بتحسين الكود الذي يعمل عليه، لكن في النهاية يمكنك إيقاف هذا السلوك و استخدامه كمجرد مُجمِّع، و ذلك كما وصفه لينوس في نفس التدوينة السابقة بقوله:

    but it was kind of fun in a "let's use the compiler as a glorified assembler" kind of way.

    و أُترجمها:
    لكن هذا كان مَرِحاً علي طريقة "فلنستخدم المُترجِم كمُجمِّعٍ ضخم" :)
* يوجد مُترجماتٌ لها إلي الأغلبية الساحقة من المُعالِجات، في الواقع يُقال أنه يمكنك أن تكتب برامج باستخدام لغة الـC لأي مُعالِج ظهر فوق سطح الأرض. و  أنا أظن أنك لو سألتَ كاتب سيناريو فيلم (يوم الاستقلال independence day) عن كيفية وضع أبطال الفيلم لفيروسٍ أرضيٍ علي حواسيب أجهزة المركبة الفضائية الأم الخاصة بغزاة الفضاء: فعلي الأرجح سينظر لك بقرفٍ و يقول" يا لك من أحمق: إنهم بالطبع يستعملون لغة الـC و نظام الـwindows"  :)
* توجد مجموعةٌ من أقوي المُترجِمات للـC كبرامج مفتوحة المصدر، علي الأرجح سمعتم عن الـgcc الخاص بمؤسسة الـfsf، و الذي كانت نواته الأولي خاصةً بلغة الـC فقط ثم تم ضم العديد من اللغات الأخري إليه ليصير تجميعةً من المُترجِمات. الحقيقة أنه يتم استخدام الـgcc علي نطاقٍ واسعٍ للغاية حتي في التطبيقات التجارية، و ربما لو ضغطتَ علي نفس كاتب السيناريو لقال لك أن غزاة الفضاء يستخدمون الـgcc لبناء برامجهم :)



هل هناك مُترجِماتٌ طبَّقت هذا الأمر عملياً ؟:
هناك مترجماتٌ قامت بالفعل باستخدام لغة الـC كتمثيلٍ وسيط، و منها مُترجِماتٌ عديدةٌ للغة الـeiffel، و جاء في موسوعة الويكيبيديا عن هذه النقطة ما نصه:

Although there is no direct connection between Eiffel and C, many Eiffel compilers (Visual Eiffel is one exception) output C source code as an intermediate language, to submit to a C compiler, for optimizing and portability.

و أُترجمه:
علي الرغم من أنه ليست هناك صِلةٌ مُباشِرةٌ بين الـeiffel و الـC، إلا أن العديد من مُترجِمات الـeiffel (الـVisual Eiffel استثناءٌ واحد) تُخرِج كود C كلغةٍ وسيطة؛ لإرساله إلي مُترجِم C؛ بسبب التحسين و المحمولية.

و بالإضافة إلي لغة الـeiffel فأظن أن هناك لغات برمجةٍ وظائفيةٍ functional programming languages فعلت المِثل، و إن كنتُ لا أتذكرها الآن.


هل من الممكن أن أفعل هذا مع مُفسِّر (أُبْدِع) ؟:
بصراحةٍ: فكرة الاعتماد علي لغة الـC كلغة تمثيلٍ وسيطٍ في أُبْدِع (حينما يتحول إلي مفسِّرٍ/مُترجِم) تُلح علي رأسي بقوة؛ فأنا أحتاج لتوفير كثيرٍ من الوقت لإخراج المواصفات القياسية النهائية من لغة إبداع إلي النور، و استخدام الـC سيوفر عليَّ أغلبية هذا الوقت. لكن المشكلة أنني سأضطر في النهاية (و في كل الأحوال) إلي بناء الواجهة الخلفية بنفسي لعديدٍ من الأسباب ليس هنا محلها !
لذا فمن الصعب حسم هذه المسألة حالياً.

شيءٌ أخير: هذا المقال يُظهِر أنني أكره الـC حينما يتم تقديمها كلغةٍ "عامَّة الأغراض عالية المُستوَي" بل و يثير هذا جنوني إلي أقصي الحدود، أما حينما يتم تقديمها كلغة "تجميعٍ محمولةٍ" يتحول كل كرهي لها إلي حبٍ و افتتان. ليس الأمر عجيباً إذا ما قارنتم بين رؤيتي للغات عالية المُستوَي عامة الأغراض و بين صفات لغة الـC و الأغراض  التي صُمِّمَت لأجلها.

أُشجِّع القراء علي الاستفادة من هذه المقالة في كتابة ورقةٍ علميةٍ شاملةٍ وافية عن هذا الموضوع، و ربما أقوم بفعل ذلك بنفسي في المستقبل بمشيئة الله تعالي؛ فالأمر يحتمل الكثير من البحث و التنقيب و الدراسة من كل النواحي، و لم أُجْمِل في الحديث عنه (رغم شغفي الشديد به) إلا لضيق الوقت و وجود الكثير من الشواغل الأخري.