כדי להתחיל לדבר על תנאים נרצה לדבר על register מיוחד, כבר ציינו ב מבנה התוכנית באסמבלי שיש שתי רגיסטרים מיוחדים שקשורים למחסנית, כעת נדבר על ה flags register, הregister הזה מחזיק בתוכו מספר ביטים חשובים, (נתמקד בארבעת הבאים)
אם התוצאה של פעולה אריתמטית היא 0, אז הflag הזה יידלק כלומר ערכו יהיה 1. למשל
מקבל והתוצאה של מספר אם התוצאה היא שלילית כלומר MSB הוא 1.
משתמשים בו עבור חישובים של unsigned.
ערכו יהיה אחד אם התוצאה גדולה יותר מהקיבולת של אופרנד. למשל
נשים לב שרק לפי הפקודות שנשתמש בהם הקומפיילר יודע האם העבודה היא עם signed או unsigned .
נראה פעולה שעבורה ה carry flag היה כבוי אם היינו עובדה באריתמטיקה של signed
התוצאה אם אנחנו עם unsigned יוצאת 255 שזה כמובן שגוי ולכן ה cf ידלוק, אבל במצב של signed היינו מקבלי
משתמשים בו לחישובים של signed
הוא יידלק כאשר, ה sign bit של האופרנדים כבוי אבל של התוצאה הוא דולק , או ההפך.
למה צריך את ה register הזה?
נוכל לבדוק שיוויון או אי שיוויון בין שתי registers או כתובות או מספרים, על ידי ביצוע פעולה ובדיקה ה flag registers, על ידי הפקודות הבאות:
באופן דומה יש לנו את אותם הפקודות עבור quad words.
למשל האם נרצה לבדוק האם ערכו של מספר הוא
testl %ecx,%ecx and check if ZF=1
אם נרצה לבצע השמה של הפלאגים בregisters משלנו נוכל להשתמש בפקודות הבאות
נשים לב שיש כאן פעולות של above ו greater שאחת בודקת השוואת ערכים ואחת השוואה לוגית לפי הביטים.
הפקודה jump מאפשר לנו לבצע קפיצה לlabel מסויים בצורה מותנת (לפי ה flags register) או בצורה בלתי מותנת. יש לזה חשיבות גבוהה מאוד ביצירת לולאות ותנאים שתיכף נראה.
בהינתן לייבל L נוכל באמצעות jmp L
להזיז את ה program counter ל L ולהמשיך משם את הקוד, נשים לב שבמצב זה לא מצופה מהתוכנית לחזור לנקודה שממנה נקרא ה jmp.
ליכולת הנ״ל קוראים direct jump. נוכל לבצע גם undirect jump באמצעות register באופן הבא
jmp *%rax
- קפיצה לכתובת שהיא הערך שנמצא בתוך rax. הכוונה בערך זה ה immediatejmp *(%rax)
- ללכת לכתובת בזכרון שרשומה בתוך rax , ולקפוץ לשם.קפיצה בהתאם לערכים שנמצאים ב register flags כפי שרשום בטבלה הבאה
במצב של קפיצה מותנת ניתן לתת רק label.
הרעיון הוא שאם נרצה למשל לבדוק תנאי, אנחנו נשתמש בפקודות האריתמטיקה שמשנות את הflag הזה, ולאחר מכן נוכל להשתמש בקוד הזה כדי לבצע scpoing בהתאם להאם התנאי התקיים או לא.
נוכל לתאר התניות באסמבלי בצורת פסודו קוד באופן הבא
t = boolean-expression;
if (t)
goto true;
else
goto done;
true:
print("true")
done:
print("done")
בעצם אנחנו קופצים לשורת קוד מסויימת תוך הזנחה של כל מה שנמצא מאחורי השורה שאליה קפצנו.
ובפועל:
נמיר כעת את הקוד c הבא לקוד אסמבלי`
int absdiff(int x, int y) {
if (x<y)
return y-x;
else
return x-y;
}
ובאסמבלי
# x in %edi, y in %esi
cmpl %esi,%edi
jl .L3
subl %esi, %edi
jmp .L5
.L3:
subl %edi, %esi
movl %esi, %eax
.L5:
נוכל לתאר לולאות do while באסמבלי בצורת פסודו קוד באופן הבא
loop:
//some code
t = bool;
if (t)
goto loop;
והמרה של קוד c לקוד אסמבלי
באופן די דומה נוכל לתאר while loops
if(!boolean)
goto done;
loop:
body-statement
t = boolean
if (t)
goto loop:
done:
למשל עבור הקוד הזה
int loop_while(int a, int b) {
int i = 0;
int result = a;
while (i < 256) {
result += a;
a -= b;
i += b;
}
}
הקוד אסמבלי ייראה כך
xorl %ecx, %ecx
movl %edi, %edx
.L5:
addl %edi ,%edx
subl %esi, %edi
addl %esi, %ecx
cmpl $255, %ecx
jle .L5
נוכל לתאר לולאות for באסמבלי בצורת פסודו קוד באופן הבא
init-expr;
t = test-expr;
if(!t)
goto done;
loop:
body-statement
update-expr;
t = test-expr;
if(t)
goto loop;
done:
ובהמרת קוד c ל assembly
long loop(long x, long y, long n) {
long result = 0;
long i;
for(i = n - 1 , i >=0 , i = i-x) {
result += x * y;
}
return result;
}
# x in rdi, y in rsi , n in rdx
xorq %rax, %rax
decq %rdx
js. L4
imulq %rdi, %rsi
.L6:
addq %rsi, %rax
subq %rdi, %rdx
jns .L6
נשים לב שהאופטימיזצייה כאן היא לעשות את הפעולה שעושים t פעמים מחוץ ללולאה כיוון שגם התנאי נעשה מחוץ ללולאה
נזכר רגע איך זה נראה ב c
switch (expression)
{
case constant1:
// statements
break;
case constant2:
// statements
break;
.
.
.
default:
// default statements
}
באסמבלי אנחנו נממש את זה עם jump table.
בהנחה שהcases הם מספרים שיחסית קרובים אחד לשני למשל בין 100 ל 106. נבנה את ה jump table באופן הבא
.section .rodata
.align 8
.L10:
.quad.L4 #case 100
.quad.L3 #case 101, not exists, sends to defult
.quad .L5 #case 102
.quad .L6 #case 103
.quad .L7 #case 104
.quad .L8 #case 105
.quad .L9 #case 106
הפקודה .align
מבקשת שכל הכתובות של הערכים שבdata שנמצאים אחרי הפקודה להיות בכפולות של 8. זה לא פעולה שנתמכת על ידי רוב הקומפיילרים. באופן כללי חייבת להיות alignment מסויים למשתנים מסוגים שונים. למשל , משתנים בגודל word חייבים להיות בכתובות שמתחלקים ב 4. הקומפיילר לרוב יבצע התאמה על ידי padding של 16 במחסנית באופן דיפולטי עבור כל משתנה מקומי ועבור כל קריאה לפונקצייה שנעשה.
פקודות מהסוג הזה נקראות directives.
אם כן מה שעשינו זה לתת לכל הLabels שיצרנו כתובות שמופרדות זה בזה על בהפרש של 8 בייטים.
כעת נוכל להשתמש ב load effective address כדי לגשת לאזור המתאים
leaq -100(%rdi), %rsi
cmpq $6 ,%rsi #the condition that we want to check
ja .L9 #if > goto default - case
jmp *.L10(, %rsi, 8) #goto jump-table[rsi]
זאת בעצם הפעולה שעשינו:
נשים לב לכמה דגשים חשובים:
switch
לא מכסה את כל האפשרויות האלה, עדיין נבנה להן אופצייה ששולחת לאיזה דיפולט case
או משהו כזה..L10
היא לא פונקצייה בפני עצמה אלא label ב rodata , כלומר שהוא רק מהווה נקודת כניסה למערך.