webcourse.cs.technion.ac.il
Download
Report
Transcript webcourse.cs.technion.ac.il
236360 תורת הקומפילציה
6 הרצאה
שפות ביניים
Intermediate Languages/Representations
Aho, Sethi and Ullman – Chapter 6
Cooper and Torczon – Chapter 5
1
יצירת קוד ביניים
syntax analysis
syntax tree
semantic analysis
decorated syntax tree
intermediate code generator
intermediate code
machine independent optimizations
intermediate code
code generator
2
חשיבות קוד הביניים
שימוש בשיטות אופטימיזציה שאינן תלויות במכונה מסויימת
אפשרות לייצר קוד עבור מכונות שונות באמצעות אותו front end
שימוש באותו back endעבור שפות שונות – מספר front ends
אם כתבנו front-ends mו ,back ends n-אז ניתן לשלב אותם ולקבל
n*mקומפיילרים.
C#
Pascal
Java
C
Intermediate Language
Intel
Cray
PowerPC
3
ייצוג ביניים – intermediate representation
ייצוגים אפשריים
syntax tree
postfix notation
– three address code זו הצורה שאנו נבחר לעבוד איתה.
שני אופרנדים ותוצאה אחת.
בד"כ עובדים בשני שלבים:
תרגום מונחה דקדוק יוצר עץ ניתוח תחבירי +סימונים בצמתים
את העץ מתרגמים לthree address code -
נדבר ראשית על ייצוג העץ ,לפעמים נשתמש בDAG -
( )Directed Acyclic Graphבמקום בעץ.
4
:שלושה ייצוגים אפשריים
postfix- ו,DAGs ,decorated syntax trees
a := b * –c + b * –c
ייצוג כעץ
DAG -ייצוג כ
assign
assign
+
a
+
a
*
b
:נתון
*
uminus
c
b
*
uminus
b
c
uminus
c
postfix -ייצוג ב
a b c uminus * b c uminus * + assign
5
תרגום מונחה דקדוק ליצירת עץ מעוטר:דוגמה
פונקציות עזר
– יצירת עלהmkleaf
– יצירת צומת חדש עבור אופרטור אונריmkunode
– יצירת צומת חדש עבור אופרטור בינאריmknode
– מצביע לטבלת הסמליםid.place
production
S → id := E
E → E1 + E 2
E → E1 * E2
E → – E1
E → ( E1 )
E → id
semantic rule
S.nptr := mknode ( ' assign ' , mkleaf ( id , id.place ), E.nptr )
E.nptr := mknode ( ' + ' , E1.nptr , E2.nptr )
E.nptr := mknode ( ' * ' , E1.nptr , E2.nptr )
E.nptr := mkunode ( ' uminus ' , E1.nptr )
E.nptr := E1.nptr
E.nptr := mkleaf ( id , id.place )
בפונקציותmknode - ו,mkunode ,mkleaf הערה – אפשר להחליף את
DAG המחזירות מצביע לצמתים קיימים על מנת ליצור
6
ייצוג בזיכרון של עץ מעוטר
a := b * –c + b * –c
assign
*
7
id
b
uminus
id
c
id
a
+
*
id
b
uminus
id
c
0
id
b
1
id
c
2
uminus
1
3
*
0
4
id
b
5
id
c
6
uminus
5
7
*
4
6
8
+
3
7
9
id
a
10
assign
9
11
···
2
8
three address code
אחרי שבונים את העץ ,צריך לתרגם לשפת הביניים שבחרנו.
אנו נעבוד עם .three-address-code
הצורה הכללית של פקודה:
אופרטור
x := y op z
↑
↑
↑
3הכתובות
,y ,xו z-הם 3שמות ,קבועים ,או משתנים זמניים שנוצרו ע"י הקומפיילר.
opהוא אופרטור כלשהו.
האופרטורים שנשתמש בהם יהיו פשוטים ,כך שיהיה קל לעבור מהם לשפת
מכונה.
8
three address code
assign
assign
+
a
*
b
*
unimus
c
t1
t2
t3
t4
t5
a
9
:=
:=
:=
:=
:=
:=
+
a
–c
b * t1
–c
b * t3
t2 + t4
t5
b
*
unimus
b
unimus
c
c
t1
t2
t3
a
:=
:=
:=
:=
–c
b * t1
t2 + t2
t3
קוד ביניים – סוגי המשפטים
.1
.2
.3
.4
.5
.6
משפטי השמה עם פעולה בינארית
משפטי השמה עם פעולה אונרית
משפטי העתקה
קפיצה בלתי מותנה
קפיצה מותנה
פרמטרים וקריאה לפרוצדורות
indexed assignments .7
.8השמה של כתובות ומצביעים
x := y op z
x := op y
x := y
goto L
if x relop y goto L
param x
call p, n
return y
] x := y [ i
x [ i ] := y
x := addr y
x := * y
* x := y
= relop
relational op
)(==, >=, etc.
n = actual
number of
parameters
קריאה
לפרוצדורה:
param x1
…
param xn
call p,n
10
איך בוחרים אופרטורים?
הבחירה של אוסף פקודות מתאים היא חשובה.
אוסף מצומצם:
קל ליצור קוד מכונה,
הקוד יהיה פחות יעיל ,ומעמסה גדולה יותר תיפול על הoptimizer -
הקוד יהיה ארוך והטיפול בו יהיה מסורבל
אי ניצול יכולות של מכונות חכמות
אופרטורים רבים:
קוד יותר יעיל אך קשה יותר לייצרו וממנו קשה לייצר קוד עבור מכונות
פשוטות.
11
יצירת קוד ביניים בעל 3כתובות על ידי תרגום
מונחה דקדוק
ככלל ,נניח bottom-up parsingכך שדברים מחושבים לפני שמשתמשים
בתוצאת החישוב.
השיטה – שימוש במשתנים זמניים
( S.codeאו – )E.codeתכונה המכילה את הקוד הנוצר עבור ( Sאו .)E
– E.varשם של משתנה שעתיד להכיל את הערך של E
– newtempפונקציה המחזירה שם של משתנה חדש
12
כתובות על ידי תרגום3 יצירת קוד ביניים בעל
מונחה דקדוק
production
semantic rule
S → id := E
S.code := E.code || gen ( id.var ' := ' E.var )
E → E1 + E 2
E.var := newtemp;
E.code := E1.code || E2.code || gen ( E.var ' := ' E1.var ' + ' E2.var )
E → E1 * E2
E.var := newtemp;
E.code := E1.code || E2.code || gen ( E.var ' := ' E1.var ' * ' E2.var )
E → – E1
E.var := newtemp;
E.code := E1.code || gen ( E.var ' := ' ' uminus ' E1.var )
E → ( E1 )
E.var := E1.var
E.code := ‘(’ || E1.code || ‘)’
E → id
E.var := id.var ;
E.code := ' '
13
)labels( דוגמא לשימוש בתוויות:while פסוק
S → while E do S1
S.begin:
E.code
if E.var = 0 goto S.after
S1.code
goto S.begin
S.after:
. ותוויות,נוסיף תכונות למשתנים
– פונקציה היוצרת תווית חדשהnewlabel
– תווית המסמנת את תחילתS.begin
הקוד
– תווית המסמנת את סוף הקודS.after
false – מייצג את0
···
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 ' : ' )
14
מבנה נתונים לייצוג של :3-address code
ייצוג סטנדרטי הוא ע"י רביעיות כך שכל שורה נכתבת לתוך משתנה זמני.
op, arg1, arg2, result
↑
↑
↑
מצביעים לטבלת הסמלים
arg 1
op
result
arg 2
c
uminus
)(0
b
*
)(1
c
uminus
)(2
t4
t3
b
*
)(3
t5
t4
t2
+
)(4
t5
=:
)(5
t1
t2
t1
t3
a
t1 = - c
t2 = b * t1
t3 = - c
t4 = b * t3
t5 = t2 * t4
a = t5
יתרון :פשוט +אין בעיה להעתיק ולהזיז קטעי קוד (וזה חשוב לאופטימיזציות).
עלות – מחייב לשמור את ה temporaries -בטבלת הסמלים
15
ייצוג נוסף של 3-address code
( op, arg1, arg2התוצאה מובנת כמספר השורה)
שלשות :
↑
↑
מצביעים לטבלת הסמלים או למספר הסידורי של השורה המחשבת את הערך
arg 2
)(0
)(2
)(3
)(4
arg 1
c
b
c
b
)(1
a
op
uminus
*
uminus
*
+
assign
)(0
)(1
)(2
)(3
)(4
)(5
אין צורך בresult -
אבל :אי אפשר להזיז קוד +פעולה טרנרית כמו x [ i ] := yדורשת שתי שורות
arg 2
i
)(0
arg 1
y
x
op
][=
assign
] x := y [ i
)(0
)(1
arg 2
i
y
arg 1
x
)(0
op
=][
assign
)(0
)(1
x [ i ] := y
16
ייצוג שלישי של 3-address code
– indirect triplesהשלשות מופרדות מהסדר ביניהן
arg 1
op
arg 2
c
uminus
0
b
*
1
c
uminus
2
)(2
b
*
3
)(3
)(1
+
4
)(4
a
assign
5
)(0
רשימת פקודות
Executionלפי סדר הביצוע
Order
הרצוי שלהן
10
11
0
1
15
12
עתה ניתן לשנות סדר ביצוע ,להזיז קטעי קוד ,ולחסוך במקום אם קיימות
שלשות זהות
(לא פותר את הפעולה הכפולה עבור פעולות טרנריות).
17
Typesוהקצאות זיכרון למשתנים
ניתוח ה types-חשוב מאד לבדיקת שגיאות,
אבל חשוב גם על-מנת לאפשר הקצאת מקום בגודל נכון למשתנים
במחסנית (או באובייקט) וחישוב offsetלכל אחד מהם ,ואף לחשב
כתובות בתוך מערכים.
...
משתנים קודמים
לemployee-
Offset for variable employee
מקום למשתנה employee
employee
רשומת הפעלה
למתודה
...
18
הכרזות והקצאת זכרון
.offset - תרגום מונחה דקדוק עם פעולות סמנטיות לחישוב ה:דוגמא
. עם גודל השטח שהוקצה עד עתהoffset נשמור משתנה גלובלי
.offset לכל משתנה בפרוצדורה – נכניס לטבלת הסמלים ונקבע לו
production
semantic rule
P→
PD
{ offset := 0 }
D→D D
D → T id ;
{ enter ( id.name, T.type, offset ); offset := offset + T.width }
T → integer
{ T.type := integer ; T.width := 4 }
T → real
{ T.type := real ; T.width := 8 }
T → T1 [ num ]
{ T.type := array ( num.val, T1.type ) ;
T.width := num.val T1.width }
T → *T1
T.type := pointer (T1.type ) ; T.width := 4 }
19
enter(money, real, 4)
offset = offset + 4
enter(count, int, 0)
offset = offset + 4
הכרזות
P
D4
D1
D5
D2
T1
id
T2
int
count
real
id
T3
money
T4
T1.type = int
T1.width = 4
id.name = count
20
id
T2.type = real
T2.width = 4
id.name = money
int
[
num
98
]
balances
הכרזות והקצאת זיכרון
שבו נפעיל אתtop-down בהתחלה עובד מצוין לניתוחoffset האיפוס של
?bottom-up אך מה עושים עם ניתוח. בתור הכלל הראשוןP → D
LR parsing- גם ב, וכלל שתמיד נראה ראשוןmarker נוסיף:טריק סטנדרטי
production
semantic rule
P→
PD
{ offset := 0 }
D→D D
D → T id ;
{ enter ( id.name, T.type, offset ); offset := offset + T.width }
T → integer
{ T.type := integer ; T.width := 4 }
T → real
{ T.type := real ; T.width := 8 }
T → T1 [ num ]
{ T.type := array ( num.val, T1.type ) ;
T.width := num.val T1.width }
T → *T1
T.type := pointer (T1.type ) ; T.width := 4 }
21
הכרזות והקצאת זיכרון
שבו נפעיל אתtop-down בהתחלה עובד מצוין לניתוחoffset האיפוס של
?bottom-up אך מה עושים עם ניתוח. בתור הכלל הראשוןP → D
LR parsing- גם ב, וכלל שתמיד נראה ראשוןmarker נוסיף:טריק סטנדרטי
production
P→
P D
semantic rule
P → MD
M→Є
{_____________
offset := 0 }
{ offset := 0 }
D→D D
D → T id ;
{ enter ( id.name, T.type, offset ); offset := offset + T.width }
T → integer
{ T.type := integer ; T.width := 4 }
T → real
{ T.type := real ; T.width := 8 }
T → [ num ] T1
{ T.type := array ( num.val, T1.type ) ;
T.width := num.val T1.width }
T → *T1
P
M
T.type := pointer (T1.type ) ; T.width := 4 }
Є
22
D
לסיכום – ייצוג של קוד ביניים
קוד ביניים סטנדרטי הוא חשוב ,ניתן להפריד בין ה front-end-שתלוי בשפת
המקור ,לבין ה back-end-שתלוי במכונת היעד ,ולשלב כל front-endעם
כל .back-end
השלבים הקודמים בונים עץ מעוטר (עם )attributes
נתרגם אותו אל three-address-codeשהיא שפת ביניים סטנדרטית.
אפשר לעשות זאת ע"י פעולות סמנטיות בתרגום מונחה דקדוק.
אוספים את הקוד לתוויות של משתני הדקדוק.
Three-address-codeניתן לייצוג ע"י רביעיות או שלשות (ישירות או
עקיפות).
ניתוח ה types-חיוני עבור קביעת מקום למשתנים בזיכרון ,וגם את זה ניתן
לעשות באמצעות פעולות סמנטיות בתרגום מונחה דקדוק...
23
יצירת קוד ביניים
24
יצירת קוד
אפשרות א' – צבירת קוד ב attributes-של משתנים בעץ הגזירה (למשל,
בתכונות מסוג .)codeכך עשינו עד עתה.
אפשרות ב' – יצירת קובץ המכיל את הקוד תוך כדי תהליך הקומפילציה
אפשרות זו מעשית (לגזירת )bottom-upאם לכל חוק דקדוק תכונת ה-
codeשל אגף שמאל של החוק מתקבלת משרשור תכונות ה code -של
המשתנים באגף ימין של החוק על פי סדר ידוע (אולי בצירוף מחרוזות
קבועות נוספות)
חסרון :מקשה על ביצוע מניפולציות על הקוד.
במספר שקפים הקרובים נדגים את אפשרות ב' .כמובן שניתן בקלות
לחזור לצבירת קוד בתכונות של משתני הדקדוק שבגזירה.
25
ביטויים ומשפטי השמה
דקדוק המסגרת :התוכנית מכילה הגדרות של משתנים (כמו קודם) ופרוצדורות.
} D → D ; D | T id { N D1 ; S
N→
P→MD
M→
ביטויים ומשפטי השמה:
} ) { p := lookup ( id.name ) ; if p nil then emit ( p ' := ' E.var ) else error
S → id := E
} ) { E.var := newtemp() ; emit ( E.var ' := ' E1.var ' + ' E2.var
E → E1 + E 2
Lookupמחזיר את הכתובת של המשתנה בזיכרון.
Emitפולט שורת קוד מתאימה בפורמט three-address-codeלתוך הקובץ.
הטיפול כאן (ובד"כ בהמשך) הוא לפי bottom-up parsingולכן מקצים
למשתנה מקום בפעם הראשונה שפוגשים אותו = כמשתנה השמאלי בכלל
הדקדוק.
26
ביטויים ומשפטי השמה
. התוכנית מכילה הגדרות של משתנים (כמו קודם) ופרוצדורות:דקדוק המסגרת
P→MD
M→
D → D ; D | T id { N D1 ; S }
N→
:ביטויים ומשפטי השמה
S → id := E
{ p := lookup ( id.name ) ; if p nil then emit ( p ' := ' E.var ) else error ) }
E → E1 + E 2
{ E.var := newtemp() ; emit ( E.var ' := ' E1.var ' + ' E2.var ) }
E → E1 * E2
{ E.var := newtemp() ; emit ( E.var ' := ' E1.var ' * ' E2.var ) }
E → – E1
{ E.var := newtemp() ; emit ( E.var ' := ' ' uminus ' E1.var ) }
E → ( E1 )
{ E.var := E1.var }
E → id
{ p := lookup ( id.name); if p nil then E.var := p else error }
עץ אין יותר שימוש- כשיוצאים מתת:ניתן לייעל במקום הדרוש למשתנים זמניים
. ניתן לנהל את המשתנים במחסנית.במשתנים הפנימיים שלו
27
ביטויים בוליאניים
.1- כtrue ואת0- כfalse נייצג את
E → E1 or E2
{ E.var := newtemp() ; emit ( E.var ' := ' E1.var ' or ' E2.var ) }
E → E1 and E2
{ E.var := newtemp() ; emit ( E.var ' := ' E1.var ' and ' E2.var ) }
E → not E1
{ E.var := newtemp() ; emit ( E.var ' := ' ' not ' E1.var ) }
E → ( E1 )
{ E.var := E1.var }
E → true
{ E.var := newtemp() ; emit ( E.var ' := ' ' 1 ' ) }
E → false
{ E.var := newtemp() ; emit ( E.var ' := ' ' 0 ' ) }
חשוב לשים לב – כתובת המטרה ניתנת לחישוב תוך כדי יצירת הקוד
28
חישוב ביטויים בוליאניים ע"י קפיצה
.1- כtrue ואת0- כfalse נייצג את
E → id1 relop id2 { E.var := newtemp() ; (lookup (נחפף את
emit ( ' if ' id1.var relop.op id2.var ' goto ' nextstat + 2 ) ;
emit ( E.var ' := ' ' 0 ' ) ;
emit ( ' goto ' nextstat + 1 ) ;
emit ( E.var ' := ' ' 1 ' ) }
. אבל נראה קודם דוגמא...למה זה מועיל? לחישוב מקוצר
29
ביטויים בוליאניים בייצוג מספרי – דוגמא
E
E
E
f
<
or
and
e
E
d
<
E
b
<
a
c
30
ביטויים בוליאניים בייצוג מספרי – דוגמא
E
E
E
f
<
or
and
e
E
d
<
E
b
c
<
if a < b goto 103
T1 := 0
goto 104
T1 := 1
a
100:
101:
102:
103:
31
ביטויים בוליאניים בייצוג מספרי – דוגמא
E
E
a
100:
101:
102:
103:
<
or
b
if a < b goto 103
T1 := 0
goto 104
T1 := 1
E
c
104:
105:
106:
107:
32
E
<
and
d
if c < d goto 107
T2 := 0
goto 108
T2 := 1
E
e
<
f
ביטויים בוליאניים בייצוג מספרי – דוגמא
E
E
a
100:
101:
102:
103:
<
or
b
if a < b goto 103
T1 := 0
goto 104
T1 := 1
E
E
c
104:
105:
106:
107:
<
and
E
d
e
f
if c < d goto 107
T2 := 0
goto 108
T2 := 1
108:
109:
110:
111:
112:
33
<
if e < f goto 111
T3 := 0
goto 112
T3 := 1
ביטויים בוליאניים בייצוג מספרי – דוגמא
E
E
a
100:
101:
102:
103:
<
or
b
if a < b goto 103
T1 := 0
goto 104
T1 := 1
E
E
c
104:
105:
106:
107:
<
and
E
d
e
f
if c < d goto 107
T2 := 0
goto 108
T2 := 1
108:
109:
110:
111:
112:
34
<
if e < f goto 111
T3 := 0
goto 112
T3 := 1
T4 := T2 and T3
ביטויים בוליאניים בייצוג מספרי – דוגמא
E
E
a
100:
101:
102:
103:
<
or
b
if a < b goto 103
T1 := 0
goto 104
T1 := 1
E
c
104:
105:
106:
107:
35
E
<
and
E
d
e
<
f
if c < d goto 107
T2 := 0
goto 108
T2 := 1
108:
109:
110:
111:
112:
113:
if e < f goto 111
T3 := 0
goto 112
T3 := 1
T4 := T2 and T3
T5 := T1 or T4
ביטויים בוליאניים – חישוב מקוצר
בניגוד לביטויים אריתמטיים ,בביטויים בוליאניים ניתן לחסוך בחישוב כי
לעיתים ניתן לדעת מה התוצאה כבר באמצע החישוב.
למשל ,בביטוי ,E1 or E2אם E1הוא trueהרי שלא חשוב לנו מה ערכו של
.E2
חישוב כזה נקרא lazy evaluationאו
.short circuit boolean evaluation
36
:דוגמא
a < b or (c < d and e < f) :ניזכר בביטוי של קודם
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 and T3
113: T5 := T1 and T4
37
:חישוב מקוצר
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:
תכונות של חישוב מקוצר
.1האם החישוב מקוצר שקול לחישוב רגיל?
.2מתי אסור להשתמש בחישוב מקוצר?
.3מתי חייבים להשתמש בחישוב מקוצר?
תשובות:
.1לא – יתכנו side-effectsלחישוב ביטוי בוליאני.
דוגמא קלאסיתif ( (i > 0) and (i++ < 10) ) A[i]=i else B[i]=i; :
.2כאשר הגדרת השפה לא מרשה זאת.
.3כאשר הגדרת השפה מחייבת קיצור ,והמתכנת עלול להתבסס על כך.
דוגמא קלאסית:
;)if ( (file=open(“c:\grades”) or (die) ) printfile(file
38
טיפול בהפניות בקרה.if, else, while :
נחזור לאגור את הקוד בתכונה ( )attributeבשם קוד.
ההבדל בין emitל gen :gen -מחזירה את הפקודה שנוצרה; emitמדפיסה
אותה ל.buffer -
נתבונן בקפיצות מותנות:
S → if B then S1
| if B then S1 else S2
| while B do S1
אפשרות אחת היא לעבוד כמו קודם ,לייצר קוד ל B-לייצר קוד ל ,S-ואז לייצר
קפיצה לתחילת Sאו סוף Sכתלות בערך של .B
אבל באופן יעיל יותר ,אפשר פשוט לייצר קוד שבזמן החישוב של Bיקפוץ למקום
הנכון ברגע שיתגלה מה ערכו של .B
39
טיפול בהפניות בקרה.if, else, while :
מסתבר שיש כאן בעיה עם ההחלטה לאן לקפוץ בזמן הניתוח...
כאשר מנתחים את העץ שנפרש מ B-עבור ” "if B then Sלא יודעים למה S
יתפתח ואיפה מתחיל ונגמר הקוד של ,Sאבל צריך לייצר קפיצות למקומות אלו.
השיטה – לכל ביטוי Bנצמיד שתי תוויות ,B.true :ו B.false-שהן התוויות אליהן
החישוב צריך לעבור אם Bהוא ( trueאו falseבהתאמה).
לכל פסוק Sנחזיק תווית nextשאומרת מה הכתובת של הקוד שאחריו.
S
S
then
B
if
משוואה סמנטיות
מתאימה:
B.false = S.next
לגבי ,B.trueנייצר
labelבין הקוד של B
לקוד של Sונייחס לו
את .B.true
40
התכונה next
בגזירה של פסוק ,Sנייצר את הקוד עם התווית שאחריו:
; )(S.next = newlabel
; )P.code = S.code || label(S.next
P→S
; )(S1.next = newlabel
;S2.next = S.next
S.code = S1.code || gen(S1.next ‘:’ ( || S2.code
S → S1 S2
התכונה S.nextהיא נורשת :הילד מקבל אותה כשהוא נגזר מאביו.
תכונת ה code-היא נוצרת :האבא מקבל אותה בעת גזירת ילדיו.
ה S.next label-היא סימבולית .הכתובת המתאימה לה תיוודע רק אחרי
שנגזור את כל הביטוי של .S
41
If B then S
עםB → קוד לחישובto B.true
→ קפיצות החוצהto B.false
B.true:
S קוד לחישוב
B.false:
...
S → if B then S1 {B.true := newlabel() ;
B.false := S.next ;
S1.next := S.next ;
S.code := B.code || gen ( B.true ' : ' ) || S1.code
}
הן תכונות נורשותS1.next - וB.false
היא תכונה נוצרתS.code
42
If B then S1 else S2
B.code
B.true:
S1.code
goto S.next
B.false:
S2.code
S.next:
...
→ to B.true
→ to B.false
לאB.false- וB.True
נקבעים ע"י ההורים ולא
אבל הם.ע"י הילדים
נקבעים בזמן גזירה
הוא ילד ולכןB שבה
.נחשבים נורשים
S → if B then S1 else S2 { B.true := newlabel ();
B.false := newlabel ();
S1.next := S.next ;
נורש
S2.next := S.next ;
S.code := B.code || gen ( B.true ' : ' ) || S1.code ||
נוצר
gen ( ' goto ' S.next ) || gen ( B.false ' : ' ) || S2.code
}
43
חישוב ביטויים בוליאניים על ידי הפנית בקרה
.false אם הואB.false- ולtrue B אם הערך שלB.true-נייצר קוד שקופץ ל
B → B1 or B2
{ B1.true := B.true ; B1.false := newlable() ; B2.true := B.true ;
B2.false := B.false ; B.code := B1.code || gen ( B1.false ' : ') || B2.code
}
B → B1 and B2
{ B1.true := newlabel (); B1.false := B.false ; B2.true := B.true ;
B2.false := B.false ; B.code := B1.code || gen ( B1.true ' : '( || B2.code }
B → not B1
{ B1.true := B.false ; B1.false := B.true ; B.code := B1.code }
B → ( B1 )
{ B1.true := B.true ; B1.false := B.false ; B.code := B1.code }
B → id1 relop id2 { B.code := gen ( ' if ' id1.var relop.op id2.var ' goto ' B.true ) ||
gen ( ' goto ' B.false ) }
B → true
{ B.code := gen ( ' goto ' B.true ) }
B → false
{ B.code := gen ( ' goto ' B.false ) }
?איזו צורת חישוב מוצגת כאן? מקוצרת או מלאה
44
חישוב ביטויים בוליאניים על ידי הפנית בקרה
נייצר קוד שקופץ ל B.true-אם הערך של true Bול B.false-אם הוא .false
; { B1.true := B.true ; B1.false := newlable() ; B2.true := B.true
B2.false := B.false ; B.code := B1.code || gen ( B1.false ' : ') || B2.code
}
B → B1 or B2
נתבונן לדוגמא ב.B1.false label-
הכתובת של ה label-ניתנת לחישוב רק אחרי שנדע את כל הקוד של B1וכל
הקוד שלפני .B1
45
דוגמא
S
then
B
if
B1
and
S1
B2
S -> if B then S1
true
false
S1.next = S.next;
B.true = newlabel();
B.false = S1.next;
S.code = B.code ||
B.true: || S1.code
46
דוגמא
S
then
B
if
B1
and
S1
B2
B -> B1 and B2
true
false
B -> true
B.code = goto B.true;
B -> false
B.code = goto B.false;
47
S1.next = S.next;
B1.true = newlabel();
B1.false = B.false;
B2.false = B.false;
B2.true = B.true;
B.code = B1.code ||
B1.true: || B2.code
חישוב ביטויים בוליאניים על ידי הפנית בקרה
במהלך החישוב הסמנטי נעבור על העץ מלמטה למעלה (למעשה אפשר
לעשות זאת במהלך ה )bottom-up parsing-ונבנה את הקוד ,תוך השארת
מקום ליעדי קפיצה שעדיין אינם ידועים.
לאחר מכן נצטרך לסרוק את העץ מלמעלה למטה ולשתול את יעדי הקפיצה.
האם אפשר לעשות הכל בסריקה יחידה?
48
– Backpatchingתיקון לאחור
מטרתנו להסתפק במעבר אחד על העץ בזמן היצירה שלו ,ללא המעבר הנוסף.
השיטה :נשמור לכל labelאת אוסף הכתובות של פקודות שמדלגות אליו.
ברגע שנדע את הכתובת של ה ,label-נלך על רשימת הכתובות ונכניס
בפקודות הקפיצה המתאימות את הכתובת האמיתית של ה.label-
יתרון :מעבר DFSיחיד יספיק (חישבו על אלפי שורות קוד).
חסרון :נצטרך להקצות מקום לרשימות של הכתובות.
נדגיש שפתרונות שהזכרנו בעבר לא יעבדו.
הגזירה אינה ( S-attributedיש גם תכונות נורשות ,למשל .)next
היא לא ( L-attributedהתכונות הנורשות אינן בהכרח נורשות-משמאל)
לכן לא נוכל לחשב את התכונות תוך כדי הניתוח.
49
דוגמא להבהרת הקושי
S
S2
else
S1
then
חישבו על פסוק .if-then-else
על-מנת לחשב את S1.nextצריך כבר לדעת את הקוד של כל הבלוקים ,B
,S1ו( S2-כדי לדעת מהי הכתובת שאחריהם).
מצד שני ,כדי לחשב את הקוד של S1צריך להעביר לו את ,S1.nextאו
,S.nextאבל ערך זה לא ידוע לפני החישוב של .S1
B
if
כאמור ,לא נוכל לחשב את הקוד של S1עם כל כתובות הקפיצה ,אבל נוכל
לחשב אותו עד כדי "השארת מקום" להכנסה מאוחרת יותר של .S1.next
בשיטת ה backpatching-נבנה את הקוד ונשאיר לעצמנו רשימה עבור ה-
labelהסימבולי S1.nextשל כל שורות הקוד שבהן יש קפיצה אליו.
כשנדע את ערכו של ,S1.nextנעבור על הרשימה ונעדכן.
50
פונקציות ליצירה וטיפול בהתחייבויות
) – makelist ( addrיצירת רשימת התחייבויות חדשה המכילה את הכתובת
.addrהתוצאה – מצביע לרשימה של כתובות של פקודות.
addr הוא מספר שורה ברשימת הרביעיות שלנו
המשמעות :יש לתקן את הפקודה שבשורה addrכשיתקבל מידע רלוונטי
) – merge ( p1, p2איחוד הרשימות אליהם מצביעים p1ו .p2 -מחזיר
מצביע לאיחוד הרשימות.
כלומר ,שתי הרשימות מכילות פקודות שצריכות לקפוץ לאותו מקום.
) – backpatch ( p, addrקביעת הכתובת addrככתובת הקפיצה בכל
אחת מהפקודות (רביעיות) שברשימה אליה מצביע p
51
אגירת הקוד
נניח (כהרגלנו) ניתוח 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
52
אגירת הקוד -המשך
,B.truelistו B.falselist-הן תכונות נוצרות :הצאצאים מספרים לאב איפה
יש קוד שצריך לתקן.
כאשר עולים לאב של Bעצמו ,נדע מה הכתובת הרלוונטית ונוכל לבצע
backpatchולהכניס אותה לכל הפקודות שנרשמו ברשימה.
באופן דומה ,ל S.nextlist-תכונות דומות.
נשתמש בפונקציה nextinstrשתחזיר את הכתובת של הפקודה הבאה.
53
חישוב ביטויים בוליאניים על ידי הפנית בקרה
.B לפי הערך שלB.false- או לB.true- הקוד שקופץ ל,כזכור
:קודם היה
B → id1 relop id2 { B.code := gen ( ' if ' id1.var relop.op id2.var ' goto ' B.true ) ||
gen ( ' goto ' B.false ) }
B → true
{ B.code := gen ( ' goto ' B.true ) }
B → false
{ B.code := gen ( ' goto ' B.false ) }
:backpatching ועכשיו עם
B → id1 relop id2
B → true
B → false
54
{ B.truelist := makelist ( nextinstr ) ;
B.falselist := makelist ( nextinstr + 1 ) ;
emit ( ' if ' id1.var relop.op id2.var ' goto_ ' ) || emit ( ' goto_ ' ) }
{ B.truelist := makelist ( nextinstr ) ; emit ( ' goto_ ' ) }
{ B.falselist := makelist ( nextinstr ) ; emit ( ' goto_ ' ) }
חישוב ביטויים בוליאניים על ידי הפנית בקרה
.B לפי הערך שלB.false- או לB.true- הקוד שקופץ ל,כזכור
:קודם היה
B → not B1
{ B1.true := B.false ; B1.false := B.true ; B.code := B1.code }
B → ( B1 )
{ B1.true := B.true ; B1.false := B.false ; B.code := B1.code }
:backpatching ועכשיו עם
B → not B1
B → ( B1 )
55
{ B.truelist := B1.falselist ; B.falselist := B1.truelist }
{ B.truelist := B1.truelist ; B.falselist := B1.falselist }
חישוב ביטויים בוליאניים על ידי הפנית בקרה
.B לפי הערך שלB.false- או לB.true- הקוד שקופץ ל,כזכור
:קודם היה
B → B1 or B2
{ B1.true := B.true ; B1.false := newlable() ; B2.true := B.true ;
B2.false := B.false ; B.code := B1.code || gen ( B1.false ' : ') || B2.code
}
B → B1 and B2
{ B1.true := newlabel (); B1.false := B.false ; B2.true := B.true ;
B2.false := B.false ; B.code := B1.code || gen ( B1.true ' : '( || B2.code }
:backpatching ועכשיו עם
B → B1 or M B2
B → B1 and M B2
M→
56
{ backpatch ( B1.falselist, M.instr ) ;
B.truelist := merge ( B1.truelist, B2.truelist ) ; B.falselist := B2.falselist
}
{ backpatch ( B1.truelist, M.instr ) ; B.truelist := B2.truelist ;
B.falselist := merge ( B1.falselist, B2.falselist ) }
{ M.instr := nextinstr }
טריק ה marker-הסטנדרטי
למשל B → B1 or M B2 :ו.M → -
בזמן הגזירה של Mמשיגים את הכתובת של תחילת .B1
B
B1
M
or
B1
57
תיקון לאחור
B.t = {100, 104}
B.f = {103, 105}
B.t = {100}
B.f = {101}
a
<
or
M.i = 102
b
and
B.t = {102}
B.f = {103}
c
58
<
B.t = {104}
B.f = {103, 105}
M.i = 104
B.t = {104}
B.f = {105}
d
e
<
f
תיקון לאחור
B.t = {100, 104}
B.f = {103, 105}
B.t = {100}
B.f = {101}
a
<
or
M.i = 102
b
and
B.t = {102}
B.f = {103}
c
100 if a < b goto ___
101 goto ___
59
<
B.t = {104}
B.f = {103, 105}
M.i = 104
B.t = {104}
B.f = {105}
d
e
<
f
תיקון לאחור
B.t = {100, 104}
B.f = {103, 105}
B.t = {100}
B.f = {101}
a
<
or
M.i = 102
b
B.t = {104}
B.f = {103, 105}
and
B.t = {102}
B.f = {103}
B.t = {104}
B.f = {105}
c
<
d
102
103
if c < d goto ___
goto ___
100 if a < b goto ___
101 goto ___
60
M.i = 104
e
<
f
תיקון לאחור
B.t = {100, 104}
B.f = {103, 105}
B.t = {100}
B.f = {101}
a
<
or
M.i = 102
b
B.t = {104}
B.f = {103, 105}
and
B.t = {102}
B.f = {103}
M.i = 104
B.t = {104}
B.f = {105}
c
<
d
102
103
if c < d goto ___
goto ___
e
<
f
100 if a < b goto ___
101 goto ___
104
105
61
if e < f goto ___
goto ___
תיקון לאחור
B.t = {100, 104}
B.f = {103, 105}
B.t = {100}
B.f = {101}
a
<
or
M.i = 102
b
B.t = {104}
B.f = {103, 105}
and
B.t = {102}
B.f = {103}
M.i = 104
B.t = {104}
B.f = {105}
c
<
d
102
103
if c < d goto 104
goto ___
e
<
f
100 if a < b goto ___
101 goto ___
104
105
62
if e < f goto ___
goto ___
תיקון לאחור
B.t = {100, 104}
B.f = {103, 105}
B.t = {100}
B.f = {101}
a
<
or
M.i = 102
b
B.t = {104}
B.f = {103, 105}
and
B.t = {102}
B.f = {103}
M.i = 104
B.t = {104}
B.f = {105}
c
<
d
102
103
if c < d goto 104
goto ___
e
<
f
100 if a < b goto ___
101 goto 102
104
105
63
if e < f goto ___
goto ___
IF :פסוקים עם הפנית בקרה
S → if B then M1 S1 N else M2 S2
{ backpatch ( B.truelist , M1.instr ) ;
backpatch ( B.falselist , M2.instr ) ;
S.nextlist := merge (S1.nextlist,N.nextlist,S2.nextlist ) }
N→
{ N.nextlist := makelist ( nextinstr ) ; emit ( ' goto_ ' ) }
M→
{ M.instr := nextinstr }
B → ( B1 )
{ B.truelist := B1.truelist ; B.falselist := B1.falselist }
S → if B then M S1
{ backpatch (B.truelist , M.instr ) ;
S.nextlist := merge ( B.falselist, S1.nextlist ) }
64
While :פסוקים עם הפנית בקרה
S → while M1 B do M2 S1
{ backpatch ( S1.nextlist, M1.instr ) ;
backpatch ( B.truelist , M2.instr ) } ;
S.nextlist := B.falselist ;
emit ( ' goto_ ' M1.instr ) }
S → begin L end { S.nextlist := L.nextlist }
S→A
{ S.nextlist := makelist ( ) }
L → L1 ; M S
{ backpatch (L1.nextlist , M.instr ) ; L.nextlist := S.nextlist }
L→S
{ L.nextlist := S.nextlist }
65
לסיכום
יצירת קוד משולבת בניתוח הסמנטי (בפעולות הסמנטיות)
או שפולטים קוד לבפר באמצעות ,emitאו שאוספים את הקוד כתכונה של
המשתנים הנגזרים ,וכשגומרים ,ה code-של המשתנה ההתחלתי Sהוא הקוד
הנדרש.
הניתוח של ביטויים לוגיים ופסוקי הפניית הבקרה הם יותר מסובכים.
ראשית צריך להחליט על ביצוע מקוצר (או לא).
שנית ,צריך לדאוג להכנסת כתובות הקפיצה אחרי שמגלים את ערכן.
אפשרות אחת :שימוש ב labels-סימבוליים ומעבר נוסף לעידכונם לכתובת
אמיתית.
אפשרות שניה :backpatching -מעבר יחיד על העץ ,אך שמירת רשימה של
כל המקומות אליהן צריך להכניס את הכתובת של labelמסוים .כשמגלים את
מיקום ה ,label-מעדכנים את כל הפקודות לפי הכתובות שברשימה.
66