نمایش میانی
نمایش میانی دادهساختار یا کدی است که کامپایلر یا ماشین مجازی کد منبع را پس از پردازش به آن تبدیل میکند و در نهایت به کد زبان مقصد که عموماً زبان اسمبلی است تبدیل میشود. میتوان از نمایش میانی استفاده نکرد و کد کاربر را مستقیماً به نمایش مقصد تبدیل کرد اما از نمایش میانی برای ساده کردن برخی از تغییرات در کد، مانند ترجمه به زبان سطح پایینتر و بهینهسازی، استفاده میشود. نمایش میانی باید بتواند بدون از دست دادن اطلاعات کد مبدأ را توصیف کند و بتواند به راحتی به کد زبان مقصد تبدیل شود.
از آن جایی که نمایش میانی نمایش درونی کامپایلر است، تقریباً هیچگاه کاربر برنامهساز با آن ارتباطی ندارد (مگر در موارد خاص بهینهسازی)، پس نیازی نیست که نمایش میانی از ساختهای پیچیده که برنامهنویسی را برای کاربر برنامهساز آسان میکنند بهرهای ببرد. این سادهسازی راه را برای بهینهسازی بهتر کد توسط کامپایلر باز میکند.
عملیات یک کامپایلر به دو بخش کلی عقب و جلو تقسیم میشود. در بخش جلو پس از پردازش کد کاربر، کد به نمایش میانی برده میشود و در بخش عقب کد از نمایش میانی به نمایش مقصد برده میشود. در صورتی که ویژگیهای نمایش میانی مستقل از زبان منبع باشد، میتوان از بخش عقب مشترکی برای کامپایلرهای مختلف استفاده کرد؛ بنابراین نمایش میانی خوب نمایش میانیای است که مستقل از هر ویژگی نمایش مبدأ یا مقصد باشد؛ بنابراین اگر نیاز داشته باشیم کد n زبان متفاوت را به کد m زبان متفاوت ترجمه کنیم، بدون داشتن IR مناسب به mn کامپایلر نیاز داریم اما در صورت داشتن IR مناسب این امر با داشتن n بخش جلو و m بخش عقب قابل حل است.
نمایش میانی میتواند اشکال متفاوتی داشته باشد، اعم از دادهساختار موجود در رم یا کدی به زبان ماشینی مجازی که به مورد دوم زبان میانی نیز گفته میشود.
اهمیت نمایش میانی
وظیفهٔ ترجمهٔ زبانهای متفاوت به یکدیگر وظیفهای پیچیدهاست که با شکسته شدن به دو بخش سادهتر میشود و میتواند بهتر صورت پذیرد. این شکستن با اضافه شدن نمایش میانی به فرایند ترجمه و تبدیل ترجمه به دو مرحلهٔ ترجمه از زبان منبع به زبان میانی و ترجمه از زبان میانی به زبان مقصد صورت میپذیرد.
زبان میانی مناسب سطح معناییای نزدیک به اسمبلی دارد و باید بتواند با حفظ معنا در مسیر تبدیل کد زبان منبع به کد زبان مقصد (که عموماً کد اسمبلی است) قرار بگیرد. نمایش میانیای که به درستی این خاصیت را داشته باشد میتواند به پودمانی کامپایلر کمک کند. در چنین شرایطی کامپایلر به دو بخش کلی عقب و جلو تقسیم میشود که کارشان از یکدیگر مجزا است. به واسطهٔ این جدایی در کامپایلر میتوان علاوه بر بهره بردن از طراحی سادهتر و قابل نگهداریتر، از یک کد چند بار استفاده کرد و به این صورت هزینهٔ توسعهٔ کامپایلرها را کاهش داد. بخش جلو، که وظیفهٔ ترجمهٔ دقیق حافظ معنا از زبان مبدأ به نمایش میانی را بر عهده دارد، تنها به زبان منبع وابسته است و کاملاً از بخش عقب که وظیفهٔ ترجمه دقیق از نمایش میانی به زبان مقصد را برعهده دارد و به زبان مقصد وابسته است جداست؛ بنابراین میتواند برای زبانهای مبدأ و مقصد متفاوت به صورت مجزا بخشهای عقب و جلو را طراحی کرد و در نهایت به یکدیگر متصلشان کرد. از آنجایی که زبان مقصد عموماً زبان ماشین است، عموماً بخش عقب به زبان ماشین وابسته است.
با طراحی بخشهای عقب متفاوت برای هر ماشین میتوان زبان مبدأ را به صورت مجزا از طراحی داخلی ماشین بر روی ماشینهای مختلف اجرا کرد و برای زبانهای برنامهنویسی جدید تنها کافی است که با طراحی بخش جلو زبان مبدأ به نمایش میانی تبدیل شود و مابقی مسیر با بخشهای عقبی که پیش از این وجود داشتهاست طی خواهد شد.
دوری نمایش میانی از زبان مبدأ و سادگی ذاتی آن زمینه را برای بهینهسازی کد فراهم میکند که نمایش میانی را به یکی از مهمترین پیشنیازهای بهینهسازی مناسب تبدیل میکند. با گسترش استفاده از یک نمایش میانی و استاندارد شدن آن زمینه برای بهبود پردازشگران شرکتهای مختلف در سایهٔ این دانش فراهم میشود. نمایش میانی استاندارد میتواند به حل مشکل سازگاری نرمافزارها کمک کند. همچنین نمایش میانی استاندارد میتواند به بهبود روشهای بهینهسازی کد با متمرکز کردن گروههای مختلف بر یک مسئلهٔ بهینهسازی بینجامد.
در حل مسائل توزیع شده نیز نمایش میانی به عنوان کدی مجزا از پلتفرم میتواند مورد استفاده قرار بگیرد و بر روی تمامی کامپیوترهای مجموعه اجرا شود.
نمایش میانی در برابر زبان میانی
در گونههای متفاوت کامپایلرها نمایشمیانی چهرههای متفاوتی به خود میگیرد. در صورتی که بخش عقب به عنوان یک زیربرنامه توسط بخش جلو صدا زده بشود، احتمالاً نمایش میانی یک درخت تجزیهٔ علامتگذاری شده به همراه جدول مربوطه خواهد بود؛ اما در صورتی که بخش عقب برنامهای مجزا از بخش جلو باشد، احتمالاً نمایش میانی زبان ماشینی مجازی است و به آن زبان میانی گفته میشود. به دلیل جدایی بیشتر و بیشتر میان بخش عقب و بخش جلو، به مرور زمان عمدهٔ نمایشهای میانی به صورت زبان میانی درآمدهاند.
ویژگیهای نمایش میانی
- کامل بودن
- نمایش میانی باید بتواند نمایشی کامل و تمیز از ویژگیهای زبان منبع برای تبدیل دقیق به زبان مقصد ارائه دهد.
- فاصلهٔ معنایی
- فاصلهٔ معنایی نمایش میانی با زبان مبدأ باید آنقدر زیاد باشد که با استفاده از کد نمایش میانی نتوان کد زبان مبدأ را به دست آورد. به این طریق حفظ مالکیت معنوی برنامهها آسانتر میشود.
- بیتوجهی به سختافزار
- نمایش میانی نباید هیچ پیشفرض و سوگیریای در مورد سختافزار مورد استفاده داشته باشد. این مسئله به این معنا است که زبان میانی نمیتواند خیلی سطح پایین باشد اما در عوض تضمین میکند که زبان میانی بر روی طیف گستردهای از سختافزارها قابل استفاده باشد.
- قابلیت برنامهنویسی دستی
- برنامهنویس باید بتواند همانگونه که به صورت دستی کد اسمبلی را تولید میکند و تغییر میدهد نمایشمیانی را نیز تولید کند و تغییر بدهد. این مسئله به بهینهسازی کد توسط کاربر برنامهساز کمک میکند و در زمان توسعهٔ کامپایلر به توسعهدهندگان کامپایلر امکان تست و برنامهسازی را میدهد.
- گسترشپذیری
- با پیشرفت تکنولوژی و به وجود آمدن تکنولوژیها و پارادایمهای جدید نیاز به گسترش و توسعهٔ زبانهای برنامه برنامهنویسی نیز به وجود میآید. ساختار نمایش میانی مناسب باید به گونهای باشد که بدون به وجود آوردن مشکلات سازگاری برای برنامههای مطابق با نسخههای پیشین بتواند تغییرات را بپذیرد و رو به جلو حرکت کند.
- سادگی
- هرچه نمایش میانی از ساختارهای کمتری پشتیبانی کند کد آن تنوع کمتری خواهد داشت که فرایند بهینهسازی را برای کامپایلر ساده میکند و دیگر طراح نیازی به در نظر گرفتن حالات متفاوت پیچیده برای بهینهسازی را ندارد که فرایند کامپایل کردن را پرهزینهتر و پیچیدهتر میکند.
- حفظ اطلاعات برنامه
- کد منبع اطلاعات کامل برنامه را در خود دارد که با هر مرحله ترجمه مقداری از آن از دست میرود. نمایش میانی مناسب نمایشی است که علاوه بر حفظ اطلاعات لازم برای اجرای برنامه، اطلاعات لازم برای بهینهسازی برنامه را نیز در خود نگه دارد.
انواع مختلف نمایش میانی
نمایش میانی انواع مختلفی دارد که به دو دستهٔ کلی داده ساختارها و زبان میانی تقسیم میشوند. دادهساختارها عموماً به صورت گراف یا درخت به همراه توضیحات مربوطه که در جدول قرار دارند هستند. گراف معنا و گراف کنترل جریان از این دسته بهشمار میآیند. زبان میانی نیز زبان یک ماشین فرضی است که عمدتاً به صورت پشتهای یا سهآدرسه است.
نمایش میانی میتواند سطوح مختلفی داشته باشد. در مثال زیر سه نمایش میانی در سه سطح متفاوت برای یک قطعه کد نمایش داده شدهاند.
قطعات کد به ترتیب نشانگر سطح بالا، سطح میانی و سطح پایین هستند.
(COPY, 0, i) i := 0
(LABEL, L1) L1:
(JGE, i, n, L2) if i>= n goto L2
(INDEX, a, i, t0) t0 := a[i]
(ADD, j, 2, t1) t1 := j + 2
(INDEX, t0, t1, t2) t2 := t0[t1]
(COPY_TO_DEREF, j, t2) *t2 := j
(INCJUMP, i, di, L1) i += di, goto L1
(LABEL, L2) L2:
در سطح بالا ساختار زبان منبع به صورت صریح حفظ میشود، برنامهٔ منبع قابل بازسازی است، عملوندها اشیاء معنادار هستند، آرایهها و محاسبات ساده نمیشوند، ثباتی در کار نیست و به زمان اجرای ماشین توجهی نمیشود.
(COPY, 0, i) i := 0
(LABEL, L1) L1:
(JGE, i, n, L2) if i>= n goto L2
(MUL, i, 80, t0) t0 := i * 80
(ADD, a, t0, t1) t1 := a + t0
(ADD, j, 2, t2) t2 := j + 2
(MUL, t2, 8, t3) t3 := t2 * 8
(ADD, t1, t3, t4) t4 := t1 + t3
(COPY_TO_DEREF, j, t4) *t4 := j
(ADD, i, di, i) i := i + di
(JUMP, L1) goto L1
(LABEL, L2) L2:
در سطح میانی نمایش از زبان و ماشین مستقل است، داده به اعداد صحیح و ممیز شناور فروکاسته میشود و بهینهسازی مستقل از معماری ماشین صورت میپذیرد.
(LDC, 0, r0) r0 := 0
(LOAD, j, r1) r1 := j
(LOAD, n, r2) r2 := n
(LOAD, di, r3) r3 := di
(LOAD, a, r4) r4 := a
(LABEL, L1) L1:
(JGE, r0, r2, L2) if r0>= r2 goto L2
(MUL, r0, 80, r5) r5 := r0 * 80
(ADD, r4, r5, r6) r6 := r4 + r5
(ADD, r1, 2, r7) r7 := r1 + 2
(MUL, r7, 8, r8) r8 := r7 * 8
(ADD, r6, r8, r9) r9 := r6 + r8
(TOFLOAT, r1, f0) f0 := tofloat r1
(STOREIND, f0, r9) *r9 := f0
(ADD, r0, r3, r0) r0 := r0 + r3
(JUMP, L1) goto L1
(LABEL, L2) L2:
در سطح پایین نمایش میانی به معماری ماشین نزدیک است و به معماری وابسته است، تفاوت اندکی با زبان مقصد دارد، درگیر مسائل زمان اجرا مانند مدیریت حافظه است و بهینهسازی وابسته به معماری انجام میدهد.
مثالهایی از زبان میانی
- java bytecode
- C: هرچند زبان سی به عنوان زبان میانی طراحی نشدهاست، نزدیکی آن به زبان ماشین در عین استقلال آن از ماشین این زبان را به زبان میانیای مناسب تبدیل کردهاست.
- CIL: زبان میانی مشترک ماکروسافت زبان میانیای است که برای استفاده در تمامی کامپایلرهای زبانهای .Net طراحی شدهاست.
- Parrot intermediate representation: زبان میانی پرت برای استفاده در زبانهای dynamically typed مانند پایتون و پرل طراحی شدهاست.
- TIMI
- O-Code
- Microsoft P-Code
- LLVM IR