1 / 63

תורת הקומפילציה 236360 הרצאה 9 שפות ביניים Intermediate Languages/Representations

תורת הקומפילציה 236360 הרצאה 9 שפות ביניים Intermediate Languages/Representations. Aho, Sethi and Ullman – Chapter 6 Cooper and Torczon – Chapter 5. יצירת קוד ביניים. syntax analysis. syntax tree. semantic analysis. decorated syntax tree. intermediate code generator. intermediate code.

vinaya
Télécharger la présentation

תורת הקומפילציה 236360 הרצאה 9 שפות ביניים Intermediate Languages/Representations

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. תורת הקומפילציה 236360הרצאה9 שפות ביניים Intermediate Languages/Representations Aho, Sethi and Ullman – Chapter 6 Cooper and Torczon – Chapter 5

  2. יצירת קוד ביניים syntax analysis syntax tree semantic analysis decorated syntax tree intermediate code generator intermediate code machine independent optimizations intermediate code code generator

  3. חשיבות קוד הביניים • שימוש בשיטות אופטימיזציה שאינן תלויות במכונה מסויימת • אפשרות לייצר קוד עבור מכונות שונות באמצעות אותו front end • שימוש באותו back end עבור שפות שונות – מספר front ends • אם כתבנו mfront-ends ו-nback ends, אז ניתן לשלב אותם ולקבל n*m קומפיילרים. C Java Pascal C# Intermediate Language Cray PowerPC Intel

  4. ייצוג ביניים – intermediate representation • ייצוגים אפשריים • syntax tree • postfix notation • three address code – זו הצורה שאנו נבחר לעבוד איתה. • שני אופרנדים ותוצאה אחת. • בד"כ עובדים בשני שלבים: • תרגום מונחה דקדוק יוצר עץ ניתוח תחבירי + סימונים בצמתים • את העץ מתרגמים ל- three address code • נדבר ראשית על ייצוג העץ, לפעמים נשתמש ב- DAG(Directed Acyclic Graph) במקום בעץ.

  5. שלושה ייצוגים אפשריים:decorated syntax trees, DAGs, ו-postfix • נתון: a := b * –c + b * –c • ייצוג כ- DAG ייצוג כעץ ייצוג ב- postfix a b c uminus * b c uminus * + assign

  6. דוגמה:תרגום מונחה דקדוק ליצירת עץ מעוטר • פונקציות עזר • mkleaf – יצירת עלה • mkunode – יצירת צומת חדש עבור אופרטור אונרי • mknode – יצירת צומת חדש עבור אופרטור בינארי • id.place – מצביע לטבלת הסמלים • הערה – אפשר להחליף את mkleaf, mkunode, ו- mknode בפונקציות המחזירות מצביע לצמתים קיימים על מנת ליצור DAG

  7. assign * * id a id id b b +   uminus uminus     id id c c   ייצוג בזיכרון של עץ מעוטר a := b * –c + b * –c

  8. אופרטור ↑ ↑ ↑ 3 הכתובות three address code • אחרי שבונים את העץ, צריך לתרגם לשפת הביניים שבחרנו. • אנו נעבוד עם three-address-code. • הצורה הכללית של פקודה: x := y op z • x, y, ו-z הם 3 שמות, קבועים, או משתנים זמניים שנוצרו ע"י הקומפיילר. • op הוא אופרטור כלשהו. • האופרטורים שנשתמש בהם יהיו פשוטים, כך שיהיה קל לעבור מהם לשפת מכונה.

  9. assign assign a + a + * * * b unimus b unimus b unimus c c c t1 := – c t1 := – c t2 := b * t1 t2 := b * t1 t3 := – c t3 := t2 + t2 t4 := b * t3 a := t3 t5 := t2 +t4 a := t5 three address code

  10. קוד ביניים – סוגי המשפטים relop = relational op (==, >=, etc.) n = actual number of parameters קריאה לפרוצדורה: param x1 … param xn call p,n

  11. איך בוחרים אופרטורים? • הבחירה של אוסף פקודות מתאים היא חשובה. • אוסף מצומצם: • קל ליצור קוד מכונה, • הקוד יהיה פחות יעיל, ומעמסה גדולה יותר תיפול על ה- optimizer • הקוד יהיה ארוך והטיפול בו יהיה מסורבל • אי ניצול יכולות של מכונות חכמות • אופרטורים רבים: • קוד יותר יעיל אך קשה יותר לייצרו וממנו קשה לייצר קוד עבור מכונות פשוטות.

  12. יצירת קוד ביניים בעל 3 כתובות על ידי תרגום מונחה דקדוק ככלל, נניח bottom-up parsing כך שדברים מחושבים לפני שמשתמשים בתוצאת החישוב. השיטה – שימוש במשתנים זמניים • S.code (או E.code)– תכונה המכילה את הקוד הנוצר עבור S (או E). • E.var – שם של משתנה שעתיד להכיל את הערך של E • newtemp – פונקציה המחזירה שם של משתנה חדש

  13. יצירת קוד ביניים בעל 3 כתובות על ידי תרגום מונחה דקדוק

  14. production semantic rule S →while E do S1 S.begin := newlabel ; S.after := newlabel ; S.code := gen ( S.begin ' : ' ) || E.code || gen ( ' if ' E.var ' = '' 0 '' goto ' S.after ) || S1.code || gen ( ' goto ' S.begin ) || gen (S.after ' : ' ) פסוק while:דוגמא לשימוש בתוויות (labels) S →while E do S1 • נוסיף תכונות למשתנים, ותוויות. • newlabel– פונקציה היוצרת תווית חדשה • S.begin – תווית המסמנת את תחילת הקוד • S.after – תווית המסמנת את סוף הקוד • 0 – מייצג את false

  15. ↑ ↑ מצביעים לטבלת הסמלים op arg 1 arg 2 result (0) uminus c t1 (1) * b t1 t2 (2) uminus c t3 (3) * b t3 t4 (4) + t2 t4 t5 (5) =: t5 a מבנה נתונים לייצוג של 3-address code: • ייצוג סטנדרטי הוא ע"י רביעיות כך שכל שורה נכתבת לתוך משתנה זמני. op, arg1, arg2, result • יתרון:פשוט + אין בעיה להעתיק ולהזיז קטעי קוד (וזה חשוב לאופטימיזציות). • עלות – מחייב לשמור את ה- temporaries בטבלת הסמלים t1 = - c t2 = b * t1 t3 = - c t4 = b * t3 t5 = t2 * t4 a = t5

  16. ↑ op arg 1 arg 2 מצביעים לטבלת הסמלים או למספר הסידורי של השורה המחשבת את הערך (0) uminus c (1) * b (0) (2) uminus c (3) * b (2) (4) + (1) (3) (5) assign a (4) op op arg 1 arg 1 arg 2 arg 2 (0) (0) [ ] = = [ ] x y i i (1) (1) assign assign x (0) (0) y x [ i ] := y x := y [ i ] ייצוג נוסף של 3-address code • שלשות : op, arg1, arg2(התוצאה מובנת כמספר השורה) • אין צורך ב- result • אבל: אי אפשר להזיז קוד +פעולה טרנרית כמו x [ i ] := y דורשת שתי שורות

  17. op arg 1 arg 2 uminus c 0 * b (0) 1 uminus c 2 * b (2) 3 + (1) (3) 4 assign a (4) 5 ייצוג שלישי של 3-address code • indirect triples – השלשות מופרדות מהסדר ביניהן • עתה ניתן לשנות סדר ביצוע, להזיז קטעי קוד, ולחסוך במקום אם קיימות שלשות זהות • (לא פותר את הפעולה הכפולה עבור פעולות טרנריות.) רשימת פקודות לפי סדר הביצוע הרצוי שלהן Execution Order 10 11 0 1 15 12

  18. Types והקצאות זיכרון למשתנים • ניתוח ה-types חשוב מאד לבדיקת שגיאות, • אבל חשוב גם על-מנת לאפשר הקצאת מקום בגודל נכון למשתנים במחסנית (או באובייקט) וחישוב offset לכל אחד מהם, ואף לחשב כתובות בתוך מערכים. ... משתנים קודמים ל-employee Offset for variable employee רשומת הפעלה למתודה employee מקום למשתנה employee ...

  19. הכרזות והקצאת זכרון • דוגמא:תרגום מונחה דקדוק עם פעולות סמנטיות לחישוב ה- offset. • נשמור משתנה גלובלי offset עם גודל השטח שהוקצה עד עתה. • לכל משתנה בפרוצדורה – נכניס לטבלת הסמלים ונקבע לו offset.

  20. enter(money, real, 4) offset = offset + 4 הכרזות enter(count, int, 0) offset = offset + 4 P D4 D1 D5 D2 id T1 id T2 T3 id int count real money ] balances num [ T4 T1.type = int T1.width = 4 T2.type = real T2.width = 4 int 98 id.name = count id.name = money

  21. הכרזות והקצאת זיכרון • האיפוס של offset בהתחלה עובד מצוין לניתוח top-down שבו נפעיל את P → Dבתור הכלל הראשון. אך מה עושים עם ניתוח bottom-up? • טריק סטנדרטי:נוסיף marker וכלל שתמיד נראה ראשון, גם ב-LR parsing

  22. הכרזות והקצאת זיכרון • השיטה עובדת מצוין לניתוח top-down שבו נפעיל את P → Dבתור הכלל הראשון ונאפס את offset כנדרש. אך מה עושים עם ניתוח bottom-up? • טריק סטנדרטי:נוסיף marker וכלל שתמיד נראה ראשון, גם ב-LR parsing _____________ P M D Є

  23. לסיכום – ייצוג של קוד ביניים • קוד ביניים סטנדרטי הוא חשוב, ניתן להפריד בין ה-front-end שתלוי בשפת המקור, לבין ה-back-end שתלוי במכונת היעד, ולשלב כל front-end עם כל back-end. • השלבים הקודמים בונים עץ מעוטר (עם attributes) • נתרגם אותו אל three-address-code שהיא שפת ביניים סטנדרטית. • אפשר לעשות זאת ע"י פעולות סמנטיות בתרגום מונחה דקדוק. • אוספים את הקוד לתוויות של משתני הדקדוק. • Three-address-code ניתן לייצוג ע"י רביעיות או שלשות (ישירות או עקיפות). • ניתוח ה-types חיוני עבור קביעת מקום למשתנים בזיכרון, וגם את זה ניתן לעשות באמצעות פעולות סמנטיות בתרגום מונחה דקדוק...

  24. יצירת קוד ביניים

  25. יצירת קוד • אפשרות א' –צבירת קוד ב-attributes של משתנים בעץ הגזירה (למשל, בתכונות מסוג code). כך עשינו עד עתה. • אפשרות ב' –יצירת קובץ המכיל את הקוד תוך כדי תהליך הקומפילציה • אפשרות זו מעשית (לגזירת bottom-up) אם לכל חוק דקדוק תכונת ה- code של אגף שמאל של החוק מתקבלת משרשור תכונות ה- code של המשתנים באגף ימין של החוק על פי סדר הופעתן (אולי בצירוף מחרוזות נוספות) • חסרון:לא מאפשר מניפולציות על הקוד. • במספר שקפים הקרובים נדגים את אפשרות ב'. כמובן שניתן בקלות לחזור לצבירת קוד בתכונות של משתני הדקדוק שבגזירה.

  26. ביטויים ומשפטי השמה • דקדוק המסגרת:התוכנית מכילה הגדרות של משתנים (כמו קודם) ופרוצדורות. • ביטויים ומשפטי השמה: • Lookup מחזיר את הכתובת של המשתנה בזיכרון. • Emit פולט שורת קוד מתאימה בפורמט three-address-code לתוך הקובץ. • הטיפול כאן (ובד"כ בהמשך)הוא לפי bottom-up parsing ולכן מקצים למשתנה מקום בפעם הראשונה שפוגשים אותו = כמשתנה השמאלי בכלל הדקדוק.

  27. ביטויים ומשפטי השמה • דקדוק המסגרת:התוכנית מכילה הגדרות של משתנים (כמו קודם) ופרוצדורות. • ביטויים ומשפטי השמה: • ניתן לייעל במקום הדרוש למשתנים זמניים: כשיוצאים מתת-עץ אין יותר שימוש במשתנים הפנימיים שלו. ניתן לנהל את המשתנים במחסנית.

  28. ביטויים בוליאניים נייצג את false כ-0 ואת true כ-1. • חשוב לשים לב – כתובת המטרה ניתנת לחישוב תוך כדי יצירת הקוד

  29. חישוב ביטויים בוליאניים ע"י קפיצה נייצג את false כ-0 ואת true כ-1. • למה זה מועיל?לחישוב מקוצר... אבל נראה קודם דוגמא.

  30. E E or E a < b E and E c < d e < f ביטויים בוליאניים בייצוג מספרי – דוגמא

  31. E E or E a < b E and E c < d e < f ביטויים בוליאניים בייצוג מספרי – דוגמא

  32. E E or E a < b E and E c < d e < f ביטויים בוליאניים בייצוג מספרי – דוגמא

  33. E E or E a < b E and E c < d e < f ביטויים בוליאניים בייצוג מספרי – דוגמא

  34. E E or E a < b E and E c < d e < f ביטויים בוליאניים בייצוג מספרי – דוגמא

  35. E E or E a < b E and E c < d e < f ביטויים בוליאניים בייצוג מספרי – דוגמא

  36. ביטויים בוליאניים – חישוב מקוצר • בניגוד לביטויים אריתמטיים, בביטויים בוליאניים ניתן לחסוך בחישוב כי לעיתים ניתן לדעת מה התוצאה כבר באמצע החישוב. • למשל, בביטוי E1or E2, אם E1 הוא true הרי שלא חשוב לנו מה ערכו של E2. • חישוב כזה נקרא lazy evaluation או short circuit boolean evaluation.

  37. 100: if a < b goto 103 101: T1 := 0 102: goto 104 103: T1 := 1 104: if c < d goto 107 105: T2 := 0 106: goto 108 107: T2 := 1 108: if e < f goto 111 109: T3 := 0 110: goto 112 111: T3 := 1 112: T4 := T2 andT3 113: T5 := T1 andT4 100: if a < b goto 105 101: if !(c < d) goto 103 102: if e < f goto 105 103: T := 0 104: goto 106 105: T := 1 106: דוגמא: a < b or (c < d and e < f) ניזכר בביטוי של קודם: חישוב מקוצר:

  38. תכונות של חישוב מקוצר • האם החישוב מקוצר שקול לחישוב רגיל? • מתי אסור להשתמש בחישוב מקוצר? • מתי חייבים להשתמש בחישוב מקוצר? תשובות: לא – יתכנו side-effects לחישוב ביטוי בוליאני. דוגמא קלאסית: if ( (i > 0) and (i++ < 10) ) A[i]=i else B[i]=i; כאשר הגדרת השפה לא מרשה זאת. כאשר הגדרת השפה מחייבת קיצור, והמתכנת עלול להתבסס על כך.דוגמא קלאסית: if ( (file=open(“c:\grades”) or (die) ) printfile(file);

  39. טיפול בהפניות בקרה:if, else, while. • נחזור לאגור את הקוד בתכונה (attribute) בשם קוד. ההבדל בין emit ל- gen: genמחזירה את הפקודה שנוצרה; emit מדפיסה אותה ל- buffer. • נתבונן בקפיצות מותנות: • אפשרות אחת היא לעבוד כמו קודם, לייצר קוד ל-B לייצר קוד ל-S, ואז לייצר קפיצה לתחילת S או סוף S כתלות בערך של B. • אבל באופן יעיל יותר, אפשר פשוט לייצר קוד שבזמן החישוב של B יקפוץ למקום הנכון ברגע שיתגלה מה ערכו של B.

  40. טיפול בהפניות בקרה:if, else, while. • מסתבר שיש כאן בעיה עם ההחלטה לאן לקפוץ בזמן הניתוח... • כאשר מנתחים את העץ שנפרש מ-B עבור "if B then S” לא יודעים למה S יתפתח ואיפה מתחיל ונגמר הקוד של S, אבל צריך לייצר קפיצות למקומות אלו. • השיטה – לכל ביטוי B נצמיד שתי תוויות: B.true, ו-B.false שהן התוויות אליהן החישוב צריך לעבור אם B הוא true (או false בהתאמה). • לכל פסוק S נחזיק תווית next שאומרת מה הכתובת של הקוד שאחריו. • משוואה סמנטיות מתאימה:B.false = S.next • לגבי B.true, נייצר label בין הקוד של B לקוד של S ונייחס לו את B.true. S if B then S

  41. התכונה next • בגזירה של פסוק S, נייצר את הקוד עם התווית שאחריו: • התכונה S.next היא נורשת: הילד מקבל אותה כשהוא נגזר מאביו. • תכונת ה-code היא נוצרת: האבא מקבל אותה בעת גזירת ילדיו. • ה-labelS.next היא סימבולית. הכתובת המתאימה לה תיוודע רק אחרי שנגזור את כל הביטוי של S.

  42. → to B.true קוד לחישוב B עם קפיצות החוצה → to B.false B.true: קוד לחישוב S B.false: . . . If B then S • B.false ו- S1.next הן תכונות נורשות • S.code היא תכונה נוצרת

  43. If B then S1 else S2 →to B.true →to B.false B.True ו-B.false לא נקבעים ע"י ההורים ולא ע"י הילדים. אבל הם נקבעים בזמן גזירה שבה B הוא ילד ולכן נחשבים נורשים. נורש נוצר

  44. חישוב ביטויים בוליאניים על ידי הפנית בקרה • נייצר קוד שקופץ ל-B.true אם הערך של Btrue ול-B.false אם הוא false. • איזו צורת חישוב מוצגת כאן? מקוצרת או מלאה?

  45. חישוב ביטויים בוליאניים על ידי הפנית בקרה • נייצר קוד שקופץ ל-B.true אם הערך של Btrueול-B.false אם הוא false. • נתבונן לדוגמא ב-labelB1.false. • הכתובת של ה-label ניתנת לחישוב רק אחרי שנדע את כל הקוד של B1 וכל הקוד שלפני B1. • למעשה, אנו נייצר את כל הקוד עם labels סימבוליים, ואחרי כן נבצע מעבר נוסף על העץ כדי לקבוע כתובת לכל label סימבולי, ולעדכן את כתובות הקפיצה בפקודות המתאימות.

  46. Backpatching – תיקון לאחור • מטרתנו להסתפק במעבר אחד על העץ בזמן היצירה שלו, ללא המעבר הנוסף. • השיטה:נשמור לכל label את אוסף הכתובות של פקודות שמדלגות אליו. • ברגע שנדע את הכתובת של ה-label, נלך על רשימת הכתובות ונכניס בפקודות הקפיצה המתאימות את הכתובת האמיתית של ה-label. • יתרון:מעבר DFS יחיד יספיק (חישבו על אלפי שורות קוד). • חסרון:נצטרך להקצות מקום לרשימות של הכתובות. • נדגיש שפתרונות שהזכרנו בעבר לא יעבדו. • הגזירה אינה S-attributed (יש גם תכונות נורשות, למשל next). • היא לא L-attributed (התכונות הנורשות אינן בהכרח נורשות-משמאל) • לכן לא נוכל לחשב את התכונות תוך כדי הניתוח.

  47. דוגמא להבהרת הקושי S B then else if S1 S2 • חישבו על פסוק if-then-else. • על-מנת לחשב את S1.next צריך כבר לדעת את הקוד של כל הבלוקים B, S1, ו-S2 (כדי לדעת מהי הכתובת שאחריהם). • מצד שני, כדי לחשב את הקוד של S1 צריך להעביר לו את S1.next, או S.next, אבל ערך זה לא ידוע לפני החישוב של S1. • כאמור, לא נוכל לחשב את הקוד של S1 עם כל כתובות הקפיצה, אבל נוכל לחשב אותו עד כדי "השארת מקום" להכנסה מאוחרת יותר של S1.next. • בשיטת ה-backpatching נבנה את הקוד ונשאיר לעצמנו רשימה עבור ה-label הסימבולי S1.next של כל שורות הקוד שבהן יש קפיצה אליו. • כשנדע את ערכו של S1.next, נעבור על הרשימה ונעדכן.

  48. פונקציות ליצירה וטיפול בהתחייבויות • makelist ( addr ) – יצירת רשימת התחייבויות חדשה המכילה את הכתובת addr. התוצאה – מצביע לרשימה של כתובות של פקודות. • addr הוא מספר שורה ברשימת הרביעיות שלנו • המשמעות:יש לתקן את הפקודה שבשורה addr כשיתקבל מידע רלוונטי • merge ( p1, p2 ) – איחוד הרשימות אליהם מצביעים p1ו- p2. מחזיר מצביע לאיחוד הרשימות. • כלומר, שתי הרשימות מכילות פקודות שצריכות לקפוץ לאותו מקום. • backpatch ( p, addr ) – קביעת הכתובת addr ככתובת הקפיצה בכל אחת מהפקודות (רביעיות) שברשימה אליה מצביע p

  49. אגירת הקוד • נניח (כהרגלנו)ניתוח bottom-up כך שהקוד נוצר בסדר הנכון (שמאל לימין, מלמטה למעלה). • הניתוח הסמנטי יתבצע במהלך הניתוח התחבירי והקוד ייפלט לתוך buffer עם פקודת emit (פשוט כדי שיהיה נוח לחשוב על כתובות של פקודות). • אפשר גם לאסוף את הקוד בתוך תכונה, כל עוד יש דרך לשמור מצביע על שורת קוד (שעליה יתבצע backpatch). • כזכור, לכל ביטוי B הצמדנו שתי תוויות: B.true, ו-B.false שהן התוויות אליהן החישוב צריך לעבור אם B הוא true (או false בהתאמה). • עתה תהיינה לנו גם זוג רשימות : B.truelist, ו-B.falselist שאומרות באילו פקודות צריך לחזור ולעדכן את הכתובות של : B.true, ו-B.false כשמגלים את ערכיהם. • בנוסף, לכל פסוק S שעבורו החזקנו label סימבולי S.next, נחזיק עתה גם רשימה S.nextlist.

  50. אגירת הקוד - המשך • B.truelist, ו-B.falselist הן תכונות נוצרות: הצאצאים מספרים לאב איפה יש קוד שצריך לתקן. • כאשר עולים לאב של B עצמו, נדע מה הכתובת הרלוונטית ונוכל לבצע backpatch ולהכניס אותה לכל הפקודות שנרשמו ברשימה. • באופן דומה, ל-S.nextlist תכונות דומות. • נשתמש בפונקציה nextinstr שתחזיר את הכתובת של הפקודה הבאה.

More Related