زبان های سازنده: بهینه سازی به کد شما چه می کند و چگونه آن را بازخوردیم
Type: Build
Languages: Python, C, x86-64 assembly, AArch64 assembly
Prerequisites: Phase 13 · 33 (disassembly and control flow), Phase 13 · 34 (lifting to pseudocode), Phase 13 · 38 (calling conventions)
Time: ~150 minutes
اهداف یادگیری
- شماره جادویی را اخذ کنید و برای تقسیم با هر ثابت با مرز گرانلوند-مونتگمری تغییر دهید و تقسیم کننده را از ثابت های پخش شده بازیافت کنید.
- پنج راه که LLVM کاهش می دهد را نام ببرید
switchو تعداد ورودی ها، مکان جدول و ورودی ها را از این شی بخونید. - یک حلقه متمایز را به عنوان خطوط، جمع آوری کننده ها، عناصر در هر تکرار، ماسک شمارش سه برابر، کاهش و حلقه باقی مانده بخوانید.
- صداهای دم، صداهای خطی، انتخاب های بدون شاخه و حلقه هایی که تبدیل به
memsetیاmemcpy. . - گزارش هر زبان با دستورالعمل های اثبات و فرم منبع آن در x86-64 و AArch64
-O0از طریق-O3. . - نتیجه را با آنچه که Ghidra، Angr، radare2، rizin، و lldb برای همان بایت ها نشان می دهد مقایسه کنید.
مشکل
تو جمع آوری کردیidioms.cدر-O2و از يه مامور پرسيدم چيdiv7u. آره ، مامور ميخوادimulq $0x24924925, %rax, %raxو جواب داد که تابع ضرب شده با 613566757، تغییر و اضافه می کند. هر کلمه درست است. تابع محاسبه می کندx / 7. .
همون جسم يهswitchبا هفت مورد که اصلاً جهش ندارهswitchبا پنج مورد که یک میز پرتاب است، یک switchبا دو مورد که یکseteو یکcmovne، و يه حلقه ديگهnاعداد تمام که هر تکرار در دو 8 عدد کامل را اجرا می کند xmmثبت می شود و سپس بقیه را در یک حلقه دوم تمام می کند.-O0همان منبع تولید شدهdivl، هفت ميز پرتاب و يک حلقه اسکالري-O1سوئیچ هفت مورد تبدیل به یک جدول ارزش شد و حلقه ویکتور هنوز وجود نداشت.
این اشکال عبارتند از اصطلاحات. یک مرتب کننده هر یک را از یک قانون می فرستد که می توانید در منبع آن بخوانید و هر یک را می توان به ساختار آن که از آن آمده رمزگذاری کرد. رمزگذاری ریاضی است و حساب می تواند بررسی شود. درس 34 یکی از آنها را ثابت کرد، ضرب با 0x66666667این درس شناسه را برای همه آنها می سازد، آن را در 18 ساخت کد خود ما اجرا می کند، و همه 373 گزارش را با منبع C تأیید می کند.
یک زبان شکل ای با مشتق است
یک زبان لغت یک دنباله از دستورالعمل های که سازنده برای یک ساخت منبع ارسال می کند. سه چیز آن را قابل تشخیص می کند. این یک شکل ثابت دارد، اپراندها ثابت هایی را که مشتق پیش بینی می کند، دارند و شکل از آنچه یک برنامه نویس به دست می نویسد متفاوت است.
شناسه درcode/main.pyدر مورد متن llvm-objdump -d -rدروس 33 به عنوان خطی برای مرزهای دستورالعمل تاسیس شده است. این هر تابع را به دستورالعمل با آدرس، بایت، mnemonic، آپرندها و خط نقل مکان که به آن پیشی می رود تجزیه و تحلیل می کند..rodata, جایی که جدول های ارزش زندگی می کنند و .rela.rodataورودی هایی که هدف های میز پرتاب قبل از اتصال زندگی می کنند.
هر کشف يه .Idiomثبت با یکkind، یک رمزگشایی شدهsourceفرم، لیست ازevidenceآدرس هاdetailو یکverifiedپرچم از منبع C آمدهparse_source()میخوادidioms.c, هر تابع را پیدا می کند و تقسیم کننده ها را پس از /و%، ضربگرها بعد از *،switchپرونده ها و ارزش های آنها? :شرکت های فعالforیک لغت تقسیم زمانی تأیید می شود که تقسیم کننده آن در لیست تقسیم عملکرد قرار دارد. یک جدول جستجو زمانی تأیید می شود که ورودی آن با ارزش های مورد مطابقت دارد، تا یک فاکتور ثابت. یک تماس دم زمانی تأیید می شود که call call در منبع خوانده شود. گزارش دمو با یک unverifiedلیست، و مجموعه تست ادعا می کند که لیست برای همه 18 ساخت خالی است.
رمزگذاری نیاز به نام دارد. شناسه یک ارزیابی کننده نمادین کوچک را بر روی بلوک های پایه هر تابع در ترتیب فهرست می کند، با نام های پارامتر از منبع در ثبتات استدلال کنوانسیون های سیستم V و AAPCS64 از درس 38.-O0در هر ورودی بلاک، حالت های پیشروها ترکیب می شوند و در جایی که متفاوت هستند نامی که یک زبان قبلی اختصاص داده شده بر یک ثابت برنده می شود. اینگونه استcmovll %edx, %eaxدرdigestبه نظر ميادsum % 10 < 5 ? sum % 10 : 5: اصطلاح حلقه به اسم مخزنsum، زبان تقسیم به نام کوتیونتشsum / 10، و اصطلاح " ماژولو " نتیجه اش را نام داده استsum % 10. منبع نوشتهrem > 5 ? 5 : remهر دو عبارت کوچکترین عدد را می دهندremو 5، و آنها موافقندrem == 5. .
تقسیم با یک ثابت
گرانلوند و مونتگومری، "قسمت با اعداد کامل غیر متغیر با استفاده از ضرب" (PLDI 1994) ، جایگزین n / dبا ضرب و بدل انتخاب کنsو ضربگرm = ceil(2^s / d)پسfloor(n * m / 2^s)برابر استfloor(n / d)برای هرnدر محدوده زمانی که خطاe = m * d - 2^sبرای امضاء نشده 32 بتnمرزش اينهe <= 2^(s - 32). براي امضاnبا|n| < 2^31مرزش اينهe <= 2^(s - 31)، و یک علامت اصلاح می افزاید 1 زمانی که nمنفی است، چون کف به سمت اشتباه برای ضریب منفی گرد می شود.
magic_numbers(d, bits, signed)درmain.pyجستجوsدر این جستجو هر صدا ثابت در چراغ ها تولید می شود.
دستخط نشده 10
2^35 / 10 = 3435973836.8سقف اون3435973837 = 0xCCCCCCCDاشتباه اينه که0xCCCCCCCD * 10 - 2^35 = 2، و درs = 35.2^3 = 8. درs = 34اشتباه 6 در برابر مرز 4 خواهد بود، بنابراین 35 کوچکترین تغییر است.n = 123.123 * 3435973837 = 422624781951و422624781951 >> 35 = 12. .
کلاگ دقیقاً برای همین کار می کنهdiv10uدر-O2در x86-64:
text 0: 89 f9 movl %edi, %ecx
2: b8 cd cc cc cc movl $0xcccccccd, %eax
7: 48 0f af c1 imulq %rcx, %rax
b: 48 c1 e8 23 shrq $0x23, %rax0x2335ـهmovl %edi, %ecxصفر به یک ثبت کننده 64 بیت 32-بیت گسترش می دهد که فرم غیر امضا شده است. شناسه ضرب را از movl $immکه دو اپراند را تغذیه می کندimulq، نوبت رو میخواد و زنگ می زندdivisor_from_magic(0xCCCCCCCD, 35). اين تابع تلاش ميکنه2^35 // m = 10و 11 و 10 را می پذیرد چونmagic_numbers(10)همان ضرب را می دهد و به عقب می رود.
در AArch64 همون تابع هستmov w8, #0xcccd; movk w8, #0xcccc, lsl #16; umull x8, w0, w8; lsr x0, x8, #35ثابت در دو نیمه 16 بت می آید، ضربش umullو تغییر به صورت دهمی نوشته می شود. شناسه نیمه ها را جمع می کند و همان تقسیم کننده را گزارش می دهد.
دستخط نشده 7 با اضافه کردن
2^35 / 7 = 4908534052.57سقف اون4908534053 = 0x124924925، که نیاز به 33 بیت دارد. خطای 3 در برابر مرز 8 است، بنابراین تغییر 35 است، و هیچ تغییر کوچک تر کار نمی کند. ضرب 32 بیت نمی تواند ضرب را نگه دارد، بنابراین کامپایلر با 32 بیت پایین ضرب می کند0x24924925و به ترتیب ثابت سود سهام را بازمی گرداند:
text 32: 48 69 c0 25 49 92 24 imulq $0x24924925, %rax, %rax
39: 48 c1 e8 20 shrq $0x20, %rax
3d: 29 c7 subl %eax, %edi
3f: d1 ef shrl %edi
41: 01 f8 addl %edi, %eax
43: c1 e8 02 shrl $0x2, %eaxاينو به عنوانt = (n * 0x24924925) >> 32، پسq = (((n - t) >> 1) + t) >> 2. کل تغییرات32 + 1 + 2 = 35. باn = 100.t = 14،(100 - 14) >> 1 = 43،43 + 14 = 57،57 >> 2 = 14. شناسه شناسي نشان دهندهsub،shr،add،shrبعد از 32 بت تغییر، اضافه می کند 2^32برگردیم به ضربگر و 7 را بازمی گردانیم.evaluate_division()این ترتیب دقیق را اجرا می کند و آزمایش آن را باn // 7در 32 بایت ارزش حاشیه. فرم AArch64 است sub w9, w0, w8; add w8, w8, w9, lsr #1; lsr w0, w8, #2، یک دستورالعمل کوتاهتر است زیرا اضافه شده یک دستورالعمل است.
دستي امضا شده 3
2^32 / 3 = 1431655765.33، سقف1431655766 = 0x55555556، خطا 2 و خط امضا شده در s = 32.2^1 = 2.محدود بدون جایی که برای صرفه جویی باقی می ماند ، بنابراین تغییر 32 و استفاده از clangshrq $0x20. باn = -7: محصول این است-10021590362، تغییر حسابداری با 32 می دهد-3, بیت علامت تنظیم شده است، بنابراین اصلاح اضافه 1 و نتیجه -2، که-7 / 3در C. چراغ های امضا شدهdiv10sوdiv3sهر دو تا از اين جا ميکننshrq $0x3fدر یک کپی از محصول دنبال شده توسط addl. .
ثابت ها چه چیزی نیستند
تنها ضربگر هیچ تقسیم کننده ای را نام نمی دهد.0x66666667با شیفت 34 برابر 10 است. همان ضربگر با شیفت 33 برابر 5 است، همانطور که درس 34 نشان داد. بنابراین شناسنده همیشه ضربگر را با کل شیفت جفت می کند و clangMatchesAlgorithmدر حال حاضر، این دو مورد از دو مورد استفاده می کنند.magic_numbers()آنها را رد می کند، زیرا سازنده تغییراتی را با یک تعصب علامت به جای آن منتشر می کند، وmod8sدر بخش بعدی این شکل را نشان می دهد.
زبان به انتخاب کننده دستورالعمل تعلق دارد
در-O0،div10u.movl $0xa, %ecx; xorl %edx, %edx; divl %ecx.div10s.cltd; idivl %ecxدر همان جسمdigestجمع حلقه اش را با 10 تقسیم می کندimulq $0x66666667يه پرچم تفاوت رو ميگهidioms-x86_64-nofastisel-O0.oبا-O0 -mllvm -fast-isel=falseو در آن هر تقسیم ضرب است، از جملهdiv10uباmovl $0xcccccccd, %ecx; imulq %rcx, %rax; shrq $0x23. قسمت سخت افزاري انتخاب انتخاب انتخاب راهبردي سريع در-O0ضرب کاهش SelectionDAG است که LLVM UnsignedDivisionByConstantInfoوSignedDivisionByConstantInfoتولیداتdigestبلوک که تقسیم را نگه می دارد ، همچنین یکswitch، و اون بلوک به SelectionDAG رفت. شناسه هر دو فرم رو گزارش ميدهhardware idivlوmagic multiply، و این شکل نشان می دهد مخلوط-O0عملکرد
ماژولو از مقدار دنبال می کند
x % d.x - (x / d) * d. کمپایلر معادل را با زبان فوق محاسبه می کند، آن را ضرب می کند و از آن تخفیف می کند. در x86-64 ضرب به 10 است addl %ecx, %ecx; leal (%rcx,%rcx,4), %ecx، که2 * q * 5در آرک 64 يه دستور هستmsub w0, w8, w9, w0، که حساب می کنهw0 - w8 * w9باw9 = 10. شناسه از طریق کپی ها از طریق ثبت مقدار را دنبال می کند، از طریق add،lea،shlوimul، و قبول مي کنهsubوقتی فاکتور برابر با تقسیم کننده است.
وقتی همان مقدار نیز یک عبارت دیگر را تغذیه می کند، هر دو اصطلاح در گزارش باقی می مانند.-O1کمپایلر حساب می کنهsum / 10يه بارdigestو ازش برايswitchدر x86-64 حتی دو برابر را ترکیب می کند.leal (%rax,%rsi), %ediبرای ماژول وaddl %esi, %eaxبرای سوئیچ. شناسه یک تقسیم و یک ماژول را گزارش می دهد و مقدار را به عنوان مشترک نشان می دهد. گزارش که تقسیم را در اینجا از دست می دهد، می تواند به switchشاخص غیر قابل خواندن
ماژولوز با قدرت دو شکل خودش را دارد، چون کوتیوینت هرگز محاسبه نمی شود:
text 92: 8d 48 07 leal 0x7(%rax), %ecx
95: 85 ff testl %edi, %edi
97: 0f 49 cf cmovnsl %edi, %ecx
9a: 83 e1 f8 andl $-0x8, %ecx
9d: 29 c8 subl %ecx, %eaxبرای منفیxکد قبل از ماسک شدن با 7 منحرف می شود، بنابراین تراکم به سمت صفر به عنوان C نیاز دارد.xبه ضرب 8 گرد و گرد و برگ های تخفیفx % 8. آرک64 با اون کار ميکنهnegs w8, w0; and w9, w0, #0x7; and w8, w8, #0x7; csneg w0, w9, w8, mi: هر دو رو ماسک کنxو-x، بعدش با علامت انتخاب کن و رد کن.RuleSignMod2nOpt، با خلاصه "برنامه های INT_SREM را تبدیل کنید: (V + (sign >> (64-n)) & (2^n-1)) - (sign >> (64-n)) => V s% 2^n".
در-O0AArch64 نوشته شده است modulo به عنوان sdiv w9, w8, w10; mul w9, w9, w10; subs w0, w8, w9. شناسه کننده اون فرم رو هم قبول ميکنه و گزارش ميدهhardware sdiv + mul + subs. .
پنج راه برای کاهش سوئیچ
LLVM شکل یکswitchدر دو مکانSimplifyCFG::simplifySwitchLookupدر-O1و بالا و تبدیل یک سوئیچ که همه موارد تولید ثابت به یک جدول ارزش ها، زمانی که حداقل سه مورد وجود دارد و هدف می تواند ایجاد جدول جستجو. انواع جدول آن هستند SingleValueKind،LinearMapKind،BitMapKindوArrayKind.-O0لوله هرگز اين گذرگاه رو اجرا نميکنه.SwitchLowering::findJumpTablesدر انتخاب دستورالعمل، که یک میز پرتاب را در زمانی ایجاد می کند که گروه حداقل min-jump-table-entriesورودی، 4 به طور پیش فرض، و به اندازه کافی کثافت است: NumCases * 100 >= Range * MinDensityباjump-table-density10 برای عملکردهای عادی و 40 تحت-Os. آنچه که باقی می ماند به یک جستجوی دوگانه در ثابت های مورد تبدیل می شود ، با splitWorkItemانتخاب محور. چراغ ها هر نتیجه ای را یک تابع دارند.
| Function | Source | -O0 x86-64 | -O2 x86-64 | -O2 AArch64 |
|---|---|---|---|---|
linear_switch | cases 0 to 6 return 10 to 70 | jump table, 7 entries | linear map 10*k + 10, default -1 | madd and csinv |
table_switch | cases 0 to 6 return 10 to 60 then 71 | jump table, 7 entries | lookup table at .rodata+0x28 | lookup table at .rodata+0x0 |
call_switch | cases 0 to 4 call one of five pointers | jump table, 5 entries | jump table, 5 entries | comparison tree, pivot 1 |
sparse_switch | cases 1, 100, 10000, 1000000, 100000000 | compare chain | comparison tree, pivot 9999 | comparison tree, pivot 9999 |
two_cases | cases 3 and 4 return 7 and 9 | compare chain | select chain k != 3 ? (k == 4)*9 : 7 | two csel |
جدول جستجو، از اجزای موجود خوانده شده
table_switchدر-O2:
text d0: b8 ff ff ff ff movl $0xffffffff, %eax
d5: 83 ff 06 cmpl $0x6, %edi
d8: 77 0c ja 0xe6 <table_switch+0x16>
da: 89 f8 movl %edi, %eax
dc: 48 8d 0d 00 00 00 00 leaq (%rip), %rcx
00000000000000df: R_X86_64_PC32 .rodata+0x24
e3: 8b 04 81 movl (%rcx,%rax,4), %eax
e6: c3 retqمرزها رو چک کنcmpl $6; jaیعنی 7 بار معتبر.0x24اضافه کردن 4 بایت از میدان RIP نسبت به جدول در .rodata+0x28بايت ها در آن جا هستند0a 00 00 00 14 00 00 00 ...، پس نوشته ها اينه[10, 20, 30, 40, 50, 60, 71]، و آنها برابر با ارزش های مورد منبع هستند. اسم ساز این جدول را نامید.Lswitch.table.table_switchدر.sپرونده، با یک نظر@switch.table.table_switchدر این شی فقط نقل مکان و بائتهای باقی مانده است. هیچ پرتابی وجود ندارد، بنابراین نمودار کنترل جریان سه بلوک دارد.
فرم AArch64 آدرس جدول را با adrp x8وadd x8, x8, #0x0هرکدوم از اونا به سمت.rodataو بارنده ازldr w0, [x8, w0, uxtw #2]. شناسه افزونه را از addنقل مکان
نقشه خطی بدون جدول
linear_switchبازپرداخت10 * (k + 1)برایkدر 0 تا 6simplifySwitchLookupرابطه آفی را تشخیص می دهد و حسابداری را منتشر می کند:
text b0: 83 ff 07 cmpl $0x7, %edi
b3: 8d 04 bf leal (%rdi,%rdi,4), %eax
b6: 8d 4c 00 0a leal 0xa(%rax,%rax), %ecx
ba: b8 ff ff ff ff movl $0xffffffff, %eax
bf: 0f 42 c1 cmovbl %ecx, %eaxارزیابی کننده میخوادleal (%rdi,%rdi,4)مثل5*k،leal 0xa(%rax,%rax)مثل10*k + 10وcmovbبعد ازcmpl $7مثلk < 7 ? 10*k + 10 : -1. شناسه کننده سپس فرمول را با هر مورد منبع بررسی می کند و زبان مورد تایید را نشان می دهد. AArch64 به همان عبارت با madd w8, w0, w8, w9وcsinv w0, w8, wzr, lo، کجاcsinvباwzrبهره وری-1در مسیر پیش فرض
میز پرتاب، از نقل مکان ها
call_switchدر-O2دروس شکل 33 در سالgradeدر-O0.cmpl $4; ja، یکleaqبه.rodata-0x4، یکmovslqاز یک ورودی 4 بایت، addqوjmpq *%rax. پنج تا ثبت نام نقل مکان در.rela.rodataوtarget = addend - 4kمی دهد0x10c, 0x107, 0x10e, 0x110, 0x109، پنج بلوک متفاوت که هرکدوم از آن ها از طریق یک شاخص عملکرد می پرسنdispatchفکسچر این را به 16 مورد می اندازد: 16 ورودی انتقال در x86-64 و در AArch64 یک جدول فشرده از 16 بایت تک خوانده می شود ldrb w11, [x9, x8]، با 4 اندازه گیری شدهadd x10, x10, x11, lsl #2، و بر اساسadrاز اولين بلوک پرونده.[0x24, 0x74, 0x54, ...]از جدول بايت و گزارش هاcompressed: true. .
همون سوئیچ در چهار سطح
digest. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .sum / 10با موارد 6 تا 10 و ارزش های 1, 2, 4, 8, 8 سپس نتیجه را با 16 ضرب کنید. در x86-64 این یک جدول پرتاب در -O0، یک میز جستجو در -O1با نوشته ها[16, 32, 64, 128, 128]، و دوباره يه ميز پرتاب در-O2و-O3که چهار تا جعبه از آن ذخیره شده0x10،0x20،0x40،0x80از-O1به سمت بالا ضرب با 16 به موارد منتقل شده است.-O0پرونده ها 1، 2، 4 و 8 را ذخیره می کنند و ضرب بعد از تغییر باقی می ماند.-O1جدول جستجو شناسه ورودی ها را با ارزش های منبع مقایسه می کند، نسبت ثابت را پیدا می کند و گزارش می دهد foldedFactor: 16. در آرک64-O2شکل یک درخت مقایسه با محور در 8 است، تست های مساوات برای 6، 7 و 8 و یک آزمایش دامنه sub w9, w9, #9; cmp w9, #2; b.hsکه پرونده های 9 و 10 را در دو دستورالعمل پوشش می دهد.rangeTests: [[9, 10]]. .
دو پرچم اين مرزها رو جابجا ميکنه-fno-jump-tablesمی چرخدtable_switch،call_switchوdigestدر x86-64 به درختان مقایسه با محور 2 و 4 برای تابع هفت مورد تبدیل شوید.-mllvm -min-jump-table-entries=4برگ هاcall_switchدر این هدف تعداد ورود برای یک تغییر پنج مورد از تماس های غیر مستقیم تصمیم گیری نمی شد.idioms-arm64-jt4-O2.oسوابق که اجرا میشن
مجلس خلیج فارس هر دو تصمیم را در اختیار داردtree-switch-conversion.cc، با کلاس هاjump_table_cluster،bit_test_cluster،switch_decision_treeوswitch_conversion. اون-ftree-switch-conversion. در حال اجرا-O2،-fjump-tablesو-fbit-testsدر-O1، و حد دستمزد پرتاب ازcase-values-thresholdهیچ GCC روی این دستگاه نصب نشده است، بنابراین این نام ها از منبع GCC و راهنمای آن آمده اند، و Compiler Explorer جایی است که شما آنها را بررسی می کنید.
حلقه ها: متراکم، باز یا جایگزین
وکتور لوله LLVM در حال فعال شدن-O2،-O3و-Os، و از کار برميگرده-O0و-O1، یک قانون که در ژانویه 2014 به LLVM 3.4 مربوط می شود. sum_nدر-O2هر قسمت شکل را نشان می دهد:
text 1f6: 83 fe 07 cmpl $0x7, %esi
1f9: 77 09 ja 0x204 <sum_n+0x14>
206: 81 e2 f8 ff ff 7f andl $0x7ffffff8, %edx
230: f3 0f 6f 14 37 movdqu (%rdi,%rsi), %xmm2
235: 66 0f fe c2 paddd %xmm2, %xmm0
239: f3 0f 6f 54 37 10 movdqu 0x10(%rdi,%rsi), %xmm2
23f: 66 0f fe ca paddd %xmm2, %xmm1
243: 48 83 c6 20 addq $0x20, %rsi
24a: 75 e4 jne 0x230 <sum_n+0x40>
24c: 66 0f fe c8 paddd %xmm0, %xmm1
250: 66 0f 70 c1 ee pshufd $0xee, %xmm1, %xmm0
262: 66 0f 7e c8 movd %xmm1, %eax
270: 03 04 97 addl (%rdi,%rdx,4), %eax
279: 75 f5 jne 0x270 <sum_n+0x80>نگهبانcmpl $7; jaتعداد سفرها رو به زیر 8 به حلقه اسکالر بفرستدandl $0x7ffffff8دورها سفر به چند برابر 8 شمارش می شود. بدن 32 بایت در هر تکرار را به دو بار بار می کند.xmmبا این حال، در هر یک از این دستگاه ها، چهار خط 32 بیت وجود دارد، بنابراین فاکتور ویکتور 4 و فاکتور ترک 2 است.paddd، دوتاpshufd، دو تا ديگهpadddوmovdهشت خط را به یک عدد کامل کاهش دهید.0x270باقی مانده اسکالر برای آخرین عناصر 0 تا 7 است. شناسه حلقه را از لبه عقب آن پیدا می کند، سوابق SIMD را می بیند، شمارش مسیر را از paddd, اکاميوتر هاي مختلف رو مي شمرده ، مرحله رو مي بخشيد0x20در 4 و گزارش ها4 lanes x 2 accumulators = 8 elements per iteration، با ماسک، محافظ، کاهش و حلقه باقی مانده به عنوان زمینه ها.ldp q2, q3، دوتاadd v.4s،addv s0, v0.4sوfmov w8, s0. .
تعداد ثابت سفر حلقه رو حذف ميکنهsum4یک استmovdqu، کاهش وmovd، بدون لبه عقبpoly8نمی تواند ویکتور سازی کند، زیرا هر تکرار بستگی به akkumulator قبلی دارد، بنابراین کامپایلر آن را هشت بار باز می کند: هشت بار بار در تعویض 0 تا 0x1cاز%rdi، هرکدوم از آنها با 5 تغییر و یک تخفیف که محاسبه می کندacc * 31. شناسه بار ها را در تعویضات متوالی از یک پایه محاسبه می کند و گزارش می دهد loop unrolled by 8, no back edge. در AArch64 همچين باري که چهار باري به اينجا رسيدldpدوتا
وکتوريزر رو خاموش کن-fno-vectorize -fno-slp-vectorizeوsum_nبه یک حلقه مقیاس تبدیل می شود که با 8 باز می شود، با 8 addl k(%rdi,%rdx,4), %eaxوaddq $8، به علاوه يه حلقه بقيهsum4به چهار اضافه می شود. شناسه گزارش می دهدunrolled loopباcopies: 8وindexStep: 8برای اولین بار وcopies: 4برای دومین بار
درس 33 دو طرف عقب رو پيدا کردtotalدر-O2و آنها را دو حلقه نامید. این درس آنها را نام می دهد: یک بدن ویکتور و یک باقی مانده اسکالر از همان حلقه منبع. یک لبه عقب فقط یک حلقه است زمانی که هدف آن می تواند به آن برسد.is_loop()اینو چک کنه چونdigestدر AArch64 شاخه های عقب نشینی به یک بلوک پیش فرض مشترک که حلقه نیستند، وجود دارد و نسخه اول از شناسه آنها را به عنوان حلقه های ویکتور حساب می کند.
حلقه هایی که تبدیل به تماس های کتابخانه می شوند
LoopIdiomRecognizeيه حلقه اي که صفر رو در هر بايت ذخیره مي کنه رو به يه تماس به memset، از-O1بالاfillدر-O2.testl %esi, %esi; jle; movl %esi, %edx; xorl %esi, %esi; jmp memset@PLT. تماس با نقل مکان يه پرتابي هستR_X86_64_PLT32 memset-0x4، و تابعretqفقط در مسیر خالی هست.store loop recognized as memset(...)و با حلقه منبع آن را تأیید می کند، چون منبع هرگز نام نمی دهدmemset. .
copy16تماس هاmemcpyبا ثابت 16 و x86 backend آن را به طور خطی گسترش می دهدmovups (%rsi), %xmm0; movups %xmm0, (%rdi)، در محدوده ی 8 فروشگاه در هرmemcpy. در-O0همان تماس دو بايت 8 استmovqدوتا.-fno-builtinتماس تماس باقی می ماندjmp memcpy@PLTباmovl $0x10, %edxوfillاین حلقه را نگه می دارد که بعد از آن، ویکتور را به حلقه دو بایت 16 تبدیل می کندmovupsذخیره ها در هر تکرار. شناسه گزارش هاvectorized store loop: 2 x 16-byte stores = 32 bytes per iterationبراي ساختش
تماس هایی که ناپدید می شوند یا به پریدن تبدیل می شوند
tail_callerبازپرداختtail_target(x + 1). در-O0آرهaddl $0x1, %edi; callq tail_target@PLT; addq $0x10, %rsp; popq %rbp; retq. در-O2آرهincl %edi; jmp tail_target@PLTو هیچی دیگه. قاب تماس گیرنده گم شده و تماس گیرنده به تماس گیرنده برگشت. در AArch64، پرشb tail_targetباR_AARCH64_JUMP26. شناسه دار به يکjmpیاbکه یک تغییر call را به عنوان یک call call انجام می دهد. کاهش مکرر در درس 33 باید آن را به عنوان یک output function در نظر بگیرد، زیرا هدف خارج از function است.
twice_sumتماس هاleaf_add. در-O0تماس اونجاستcallq leaf_add@PLTاز-O1بدنleaf_add،leal (%rdi,%rsi), %eax، داخل ظاهر ميشهtwice_sumو هيچ تماسي باقي نماند-O0clang هر تابع رو نشان ميدهoptnoneوnoinlineو فقط دستگاه همیشه در خط رو اجرا میکنه.twice_sumتماس هاleaf_add، هیچ تماس یا دم پرتاب را به آن پیدا نمی کند، و سپس به دنبال شکل بدن کاللی در تماس گیرنده: mnemonic و منبع operands با مقصد حذف شده است، بنابراین add w8, w1, w0در تماس با AArch64add w0, w1, w0یه تطابق شواهد
dispatchهر دو رو ترکیب ميکنه: يه ميز پرش 16 تا که هر موردشmovl %esi, %edi; jmp fN@PLT. شناسه یک سوئیچ و 16 تماس عقب را گزارش می دهد، همه آنها با 16 تماس در منبع تأیید شده اند.
شاخه هایی که به داده تبدیل شدند حرکت می کنند
clamp_max.movl %esi, %eax; cmpl %esi, %edi; cmovgl %edi, %eax. نام هاي معين کننده%ediمثلaو%esiمثلb, مقایسه را ثبت می کند و می خواندcmovgمثلa > b ? a : b، که متن منبع است.abs_int.movl %edi, %eax; negl %eax; cmovsl %edi, %eaxو به این شکل میخواد-x < 0 ? x : -x؛ آرک64 نوشته کهcmp w0, #0; cneg w0, w0, miو به این شکل میخوادx < 0 ? -x : x.is_pos.xorl %eax, %eax; testl %edi, %edi; setg %al، که به عنوان(x > 0)اينها-fif-conversionدر کلمات شورای خلیج فارس وcmovانتخاب در LLVMs. LLVMs X86CmovConversionگذر می تواند یکcmovوقتی که این بخش سودآور است، به شاخه ها برگردد، به همین دلیل است که همان منبع می تواند هر دو شکل را در یک حلقه نشان دهد.
در-O0هيچکدوم از اين ها وجود ندارهclamp_maxیک مقایسه است.jle، دوتا مغازه به يه سمت و يه بار-O0وbranchless selectاز-O1، که یکی از واقعیت هایی است که این رقم نشان می دهد.
mul9یک استleal (%rdi,%rdi,8), %eax، و گزارش های شناسهstrength reductionباx * 9، چون منبع به 9 ضرب می شود. این فقط زمانی اتفاق می افتد که منبع دارای عامل باشد.leaکه مقیاس یک شاخص جدول ضرب نیست.
دو ویژگی سختگیرانه که شبیه لغز هستند
محافظ سکه یک ویژگی کامپایلر است و شکل ای را می گذارد که یک عامل به عنوان منطق برنامه خواندن می کند. canary.cبا -fstack-protector-strongبرای x86-64 لینوکس می دهد:
text 7: 64 48 8b 04 25 28 00 00 00 movq %fs:0x28, %rax
10: 48 89 44 24 40 movq %rax, 0x40(%rsp)
26: 64 48 8b 0c 25 28 00 00 00 movq %fs:0x28, %rcx
2f: 48 3b 4c 24 40 cmpq 0x40(%rsp), %rcx
34: 75 05 jne 0x3b <with_canary+0x3b>
3b: e8 00 00 00 00 callq 0x40 <plain_leaf>
000000000000003c: R_X86_64_PLT32 __stack_chk_fail-0x4%fs:0x28.stack_guardدر گليبکtcbhead_t، در بايت آفسه 40 و LLVM getIRStackGuardنام هایی که برای glibc، bionic و Fuchsia جای می دهند. پیشگویی محافظ بالای بفر را کپی می کند، اپیلوگ مقایسه می کند و مسیر شکست صدا می کند__stack_chk_failدر AArch64 لينوکس نگهبان از سطح جهانی است__stack_chk_guardاز طریق GOT با adrpوldrو شکستشbl __stack_chk_fail. شناسه کننده منبع نگهباني، محل، مقایسه و تماس شکست را گزارش ميدهplain_leaf، که هیچ آرایه ای ندارد، هیچ کاناری زیرش نیستstrong, که از تابع ها با آرایه ها محافظت می کند,allocaدر یادداشت های تحقیقاتی نیز فرم MSVC زیر ثبت شده است/GS، کوکی مخلوط شده با%rspو توسط__security_check_cookie; هیچ لینکر MSVC در این دستگاه وجود ندارد، بنابراین این شکل تنها از تولید مجموعه سازنده توصیف می شود.
-fcf-protection=fullمی گذاردendbr64، بائتهايf3 0f 1e faاین یک پاد فرود برای شاخه های غیر مستقیم در پردازنده ها با اجرای جریان کنترل است و به عنوان یکnopالگوهای شروع عملکرد x86-64 گیدرا شاملendbr64متغیرهای پیش فرض از push rbp; mov rbp, rspبه همین دلیل، شناسه آن را به عنوانcet landing pad، بنابراین یک تابع که با چهار بایت غیرمعمول شروع می شود، برای داده اشتباه گرفته نمی شود.
آنچه که جمع کنندگان می کنند
هر دکامپایلر در درس 37 برخی از این مشتق ها را به عنوان قوانین بازنویسی اجرا می کند.ruleaction.cc.RuleDivOpt"تغییر INT_MULT و تغییر فرم ها به INT_DIV یا INT_SDIV" است، با فرم ها sub( (zext(V)*c), d) >> e => V / (2^n/(c-1)) where n = d*8 + eو نسخه امضا شده با sextوs>>.RuleModOpt"عبارات ساده ای که INT_REM و INT_SREM را بهینه می کنند".RuleDivTermAddبا اصلاحات اضافی کار می کندRuleSignDiv2می چرخد(V + -1*(V s>> 31)) s>> 1بهV s/ 2وRulePositiveDivدر درس 37 Ghidra 12.1.4 در درس 33 گرفته شده است.gradeدر-O1، و خروجی میخوادuVar2 = param_1 / 10 - 6;. بخش بسته شده
ورودی به این قوانین بدون جاوا قابل مشاهده است. pypcode 4.0.1 venv با همان مشخصات SLEIGH استفاده می کند Ghidra، و code/fixtures/idioms-x86_64-O2.lifts.jsonکد P خام را برای div10s: 112 مطلب برای هفت دستورالعمل که 7 مورد از آنهاIMARKنشانگرها که هر دستورالعمل رو باز مي کنن، از بينشونRAX = RAX * unique[95a00:8]بعد ازunique[95a00:8] = sext(0x66666667)،RCX = RCX >> unique[d1a00:4]وRAX = RAX s>> unique[c5c00:4].shrq41 عملیات انجام می دهد وsarq40 نفر رو حمل ميکنه و بيشترشون پرچم ها رو حساب مي کننRuleDivOptضرب و دو تغییر در این جریان را مطابقت می دهد و حذف کد مرده پرچم هایی را که هیچ چیز نمی خواند حذف می کند.
. اينگر 10.0.3 روي ما اجرا شد-O2موضوع وidioms-x86_64-O2.angr.json. در 20 تابع از محصول decompiler خود استفاده می کند . این فرم بدون امضا را طعمه می کند:div10u.return a0 / 10;. این فرم امضا شده را طوطه نمی کند: div10s.v1 = a0 * 1717986919; return ((long long)(v1) >> 0x22) + (v1 >> 63);وmod10s.a0 - (...) * 2 * 5. برایdiv7uچاپ مي کنه(a0 / 7 + ((a0 * 6 / 7 & 0xffffffff) >> 1 & 0xffffffff) & 0xffffffff) >> 2، که در آن اصطلاحات درست کردن به عنوان حسابداری زنده می ماند.mul9بهa0 * 9،linear_switchبه(a0 < 7 ? a0 * 10 + 10 : 0xffffffff)،clamp_maxبه((int)a1 < (int)a0 ? a0 : a1)،is_posبهa0 > 0وfillبهreturn memset(a0, 0, a1);. چاپ ميکنهtable_switchبه عنوان یک بار*(int *)(4 * a0 + 4195400)بدون ارزش هاsum_nبا_mm_add_epi32و_mm_shuffle_epi32درونی ها وtail_callerمثلtail_target(); return;با بحث گمشدهdigestمیز پرتاب می شودgoto *((void *)(...))با حساب حساب میز نوشته شده
رادار2 6.2.4 و ريزين 0.9.1 که از منبع به حافظه تحقیقاتی ساخته شده، روی همان جسم اجرا شده اند.pdcچاپdiv10sمثلrax = rax * 0x66666667; rcx >>>= 0x3f; rax >>= 0x22; eax += ecx, سطح ثبت نام pseudo-C بدون پوشاندن وtable_switchمثلeax = dword [rcx + rax*4]با یکRELOC 32 .rodataنظر در موردlea. ریزین هیچ دستگاه تجزیه و تحلیل داخلی نداردaoiRzIL را چاپ می کندimulبه عنوان یک محصول ۱۲۸ بایت. lldb-1703.0.236.21 بار ELF را و آن را disassemble -n div10sهمون هفت دستور رو چاپ ميکنه کهllvm-objdump، چون هر دو از جداکننده LLVM استفاده می کنند. Capstone 5.0.7 همان بائتهای را رمزنگاری می کند و گروه هایی مانند retوmode64این یک کدگر است و یک زبان زبان هرگز در خروجی یک کدگر نیست. IDA Pro و Binary Ninja در اینجا اجرا نشده است. اسناد آنها مراحل این نوع نوشتن مجدد، بهینه سازی میکرو کد و گذر MLIL به HLIL را توصیف می کند و این درس هیچ ادعایی در مورد اینکه کدام زبان زبان را بپوشند نمی کند.
کمپایلر اکسپلورر ابزار جهت پیش رو است.idioms.c، یک ورودی کامپایلر مانند "x86-64 clang 18.1.0" یا "ARM64 GCC 14.2.0" را انتخاب کنید و پرچم ها را تنظیم کنید. سرویس حدود 1,050 ورودی کامپایلر C را لیست می کند و دارای API است: GET /api/compilers/cشناسه ها را بازمی گرداند وPOST /api/compiler/<id>/compileمقاله ACM قطار 2019 Matt Godbolt از طریق تقسیم، شمارش جمعیت، حالت بندی زنجیره ای و اصطلاحات جمع بندی با این ابزار استفاده می کند. از آن برای تولید GCC استفاده کنید، که این ماشین نمی تواند تولید کند، و حد جدول پرتاب GCC را مقایسه کنید و -O2متغیرات متغیر از GCC 12 با خروجی clang در چراغ ها.
آن را بسازید
نمایش نمایش را اجرا کن:
bashcd phases/13-tools-and-protocols/39-compiler-idioms-and-optimization
python3 code/main.pyمحصول یک گزارش JSON است. برای هر یک از 18 ساخت، هر عملکرد و اصطلاحات آن را با نوع، فرم منبع، آدرس شواهد، تأیید و زمینه های جزئیات فهرست می کند.-O2در مقابل-O3مقایسه برای هر دو معماری و unverifiedلیست
شکل انتظار می رود:
json{
"builds": {
"idioms-x86_64-O2": {
"arch": "x86_64", "level": "O2", "flags": "x86_64",
"functions": {
"div7u": [{"kind": "division", "source": "x / 7", "evidence": ["0x30", "0x32", "0x39", "0x3d", "0x3f", "0x41", "0x43"], "verified": true, "form": "magic multiply with add fix-up", "magic": "0x24924925", "effectiveMagic": "0x124924925", "shift": 35, "...": "..."}],
"table_switch": [{"kind": "switch", "source": "switch (k) as lookup table", "table": ".rodata+0x28", "entries": 7, "values": [10, 20, 30, 40, 50, 60, 71], "...": "..."}],
"sum_n": [{"kind": "vector loop", "lanes": 4, "accumulators": 2, "elementsPerIteration": 8, "...": "..."}]
}
}
},
"kindMatrix": {"idioms-x86_64-O2": {"division": 5, "modulo": 3, "switch": 6, "vector loop": 3, "...": "..."}},
"sameCodeAtO2AndO3": {"x86_64": {"identical": true}, "arm64": {"identical": false, "differences": ["..."]}},
"unverified": []
}بخونcode/main.pyدر این ترتیب:
parse_listing().llvm-objdump -d -rمتن به عملکردها، دستورالعمل ها و نقل مکان های متصل برای هر دو معماری.ElfFileبخش ها، نمادها و.rela.*نوشته ها وwords()نوشته هاي جدول رو ميخوادmagic_numbers()،divisor_from_magic()وevaluate_division()مشتق گرانلوند-مونتگمری به جلو، عقب و به عنوان دنباله دستورالعمل است.parse_source()ساختارهای مورد نیاز برای تأیید را از پرونده C استخراج می کند.Evaluatorاین گذرنامه نمادیه ای بلوک به بلوک است که نام ها را ثبت می کند و اسلات را جمع می کند.find_divisions()وfind_modulos()با ضرب، تغییر، تنظیم، ضرب و برگشت و قدرت دو شکل مطابقت دارد.find_switches()جدول های پرتاب، جدول های جستجو، نقشه های خطی، زنجیره های انتخاب، درختان مقایسه و مقایسه زنجیره ها را طبقه بندی می کند.find_vector_loops()،find_unrolled_loops()،find_tail_calls()،find_inlined_calls()،find_selects()،find_memory_builtins()،find_stack_protector()،find_cet()وfind_strength_reduction()بقیه رو بپوشanalyze_function()آنها را در ترتیب ترتیب می دهد و نام نتیجه هر زبان را به گزینۀ ارزیابی کننده بعدی می دهد.
سه فرمان ديگه وجود دارهpython3 code/main.py scan FILE.objdump.txt [FUNCTION]شناسه را در یک لیست اجرا می کند، با موجود و فایل C مطابقت پذیر در کنار آن یافت می شود. python3 code/main.py magic DIVISOR [signed]ضربگر، تغییر، خطا و اینکه آیا هویت در ارزش های کناری نگه دارد را چاپ می کند. python3 code/main.py figure-dataداده های موجود را چاپ می کند.
ازش استفاده کن
از شناسه در این ترتیب در یک شی جدید که ساختی استفاده کن.
- دستگیرش کن
llvm-objdump -d -rاز موضوع و نگه داشتن دقیق کامپایلر فرمان کنار آن. شناسه معماری را از عنوان لیست می خواند. - فرار کن
scanدر لیست و مطالعه کنیدunverifiedهر یک یا یک ساختاری است که منبع تجزیه کننده نمی بیند یا یک شکل که شناسه اشتباه خوانده شده است، و هر دو سزاوار نگاهی به آدرس های شواهد هستند. - براي هر بخش، بهشون بخونيد
algorithm. میدانclangMatchesAlgorithmدرست است، کمپایلر کوچکترین تغییر معتبر را انتخاب کرد. وقتی غلط است، کمپایلر دلیل دیگری داشت که هنوز نمی دانید؛ ثابت ها را بنویسید. - برای هر سوئیچ، مقایسه کنید
loweringجدول ای که به یک درخت تبدیل می شود، یا یک درخت که به یک جدول تبدیل می شود، یک آستانه ای را نشان می دهد که می توانید از منبع LLVM نام ببرید. - برای هر حلقه، بخون
elementsPerIterationوscalarRemainderLoop، پس تایید کنید دو لبه عقب درس 33 گزارش ها یک حلقه منبع هستند - یک کامپایلر را روی همان شی اجرا کنید، متن آن را در کنار هر لغت نقل قول کنید و آن را که طنین کرده است ثبت کنید. این ضبط خط پایه برای درس 45 است.
آزمایشگاه تعاملی
آزمایشگاه A: یک عدد جادویی بدست بیاورید، سپس آن را پیدا کنید
ازcodeدایرکتوری، پیتون باز:
bashcd phases/13-tools-and-protocols/39-compiler-idioms-and-optimization/code
python3 -qراه رفتن:
pythonfrom main import *
magic_numbers(10), magic_numbers(10, signed=True)
magic_numbers(7), magic_numbers(7, signed=True)
divisor_from_magic(0x66666667, 34, signed=True), divisor_from_magic(0x66666667, 33, signed=True)حساب کردنceil(2^35 / 10)قبل از اولین تماس روی کاغذ قرار دهید. سپس یک تابع اضافه کنید unsigned div13(unsigned x) { return x / 13; }به نسخه ای ازidioms.c، ساختش رو در-O2با فرماندهي درbuild_fixtures.sh، و ثابت رو در لیست با magic_numbers(13). .
لابراتوار ب: میز و درخت
pythonlisting = load_listing("idioms-x86_64-O2.objdump.txt")
elf = load_elf("idioms-x86_64-O2.o")
sources = load_source("idioms.c")
[i.to_dict() for i in analyze_function(listing, "table_switch", elf, sources)]
[i.to_dict() for i in analyze_function(listing, "sparse_switch", elf, sources)]
elf.words(".rodata", 0x28, 7)تغییر71برگردیم به70در نسخه ای ازtable_switch، دوباره ساختش رو در-O2و ببینید جدول جستجو تبدیل به یک نقشه خطی می شود. سپس یک مورد را به 1000 تغییر دهید و ببینید که یکArrayKindمیز
لابراتوار C: حلقه در سه تنظیم
pythonfor name in ("idioms-x86_64-O1", "idioms-x86_64-O2", "idioms-x86_64-novec-O2"):
print(name, [i.source for i in analyze_function(load_listing(name + ".objdump.txt"), "sum_n", load_elf(name + ".o"), sources)])نمودار جریان کنترل را از sum_nدر-O2با دست، محافظ، ماسک، بدن متری، کاهش و حلقه باقیمانده را نشان دهید و با detail. میدان ها
آزمایشگاه D: کوتیوین مشترک
pythonfor name in ("idioms-x86_64-O1", "idioms-arm64-O1"):
print([(i.kind, i.source, i.detail.get("sharedQuotient")) for i in analyze_function(load_listing(name + ".objdump.txt"), "digest", load_elf(name + ".o"), sources)]).-O1فهرست digestو دو دستورالعمل را پیدا کنید که این کوتیوینت را با تنظیم علامتش در x86-64 ترکیب می کند. سپس توضیح دهید که چرا ساخت AArch64 فقط به یک مورد نیاز دارد.
آزمایشگاه تمرین
به چراغ ها چند تا ترفند اضافه کنید و شناسه را گسترش دهید.
الزامات:
- بنویس
popcount،ctzوrotlعملکردهای با ساختمان های GCC و یک تغییر یا چرخش، آنها را در-O2با و بدون-mpopcnt -mbmi، و اجسام را با لیست های خود به کار ببرید - شناختن
0x55555555،0x33333333،0x0f0f0f0f،0x01010101ترتیب تعداد جمعیت کهTargetLowering::expandCTPOPارسال می کند و گزارش می دهدpopcountبا چهار ثابت به عنوان مدرک - تشخیص بده
rep bsflبه عنوانtzcnt-یا -bsfکدگذاری وroll %clبه عنوان چرخ و هر دو را به ساخت رمزگذاری. - انواع جدید را به اضافه کنید
parse_source()تایید، پس یکpopcountزبان فقط وقتی که منبع تماس می گیرد تایید می شود__builtin_popcount. . - یک آزمون برای هر زبان اضافه کنید که ثابت های لیست وسایل را می خواند.
موفقیت به معنیpython3 code/main.pyگزارش سه نوع جدید با verified: trueدر هر دو ساخت و ساز و مجموعه هنوز گزارش یک خالیunverifiedلیست
درس هنرپیشه
این درس شاملoutputs/skill-compiler-idioms-and-optimization.md. از آن برای تبدیل یک لیست، یک شی و منبع آن به یک گزارش زبان معین با مقایسه decompiler استفاده کنید. این نیاز به آدرس شواهد برای هر ادعای، مشتق عدد جادویی برای هر تقسیم، نوع کاهش و منبع ورودی برای هر سوئیچ و یک خط decompiler برای هر زبان است.
بررسی کنید
نمایش و مجموعه تعیین کننده را اجرا کنید:
bashcd phases/13-tools-and-protocols/39-compiler-idioms-and-optimization
python3 code/main.py
python3 -m unittest discover -s code/tests -vبررسی باید ثابت کند:
- در فهرست پارسر 25 تابع از
-O2موضوع، بايت ها رو نگه ميده و ميچشونهR_X86_64_PC32 .rodata+0x24بهleaqدرtable_switch - خواننده ELF باز می گردد
[10, 20, 30, 40, 50, 60, 71]از.rodata+0x28و ده بار در شهر.rela.rodata magic_numbersبازپرداخت0xcccccccdبا نوبت 350x66666667با 340x124924925با 35 و فرم اضافه،0x55555556با 32، و0xaaaaaaabبا ۳۳- هویت بر روی 32 بیت ارزش حاشیه برای هر تقسیم کننده از 2 تا 64 که قدرت دو، امضا شده و امضا نشده نیست، برقرار است
divisor_from_magic10، 5، 7 و 3 را از جفت های خود بازمی گرداند و جفت اشتباه را رد می کند- حسابدار های دنباله ی اضافه کردن
n // 7دقیقا - چهار بخش x86-64 ضربگر راست، تغییر، تقسیم کننده و شکل را گزارش می دهند و چهار بخش AArch64 رمزگذاری می کنند
movوmovk -O0استفادهdivlوidivl،-O0 -fast-isel=falseاستفاده از ضرب وdigestدر-O0دوتا رو مخلوط ميکنه- ماژولو به عنوان ضرب باز شناخته می شود.
msub، قدرت دو،sdivو اضافهmulو اضافهsubsو سخت افزارidivl - یک کوتیوینت مشترک هم تقسیم و هم ماژول را در نگه می دارد
-O1 - پنج کاهش در
-O2در هر دو معماری، با فرمول، ارزش ها، اهداف، محورها و بیان انتخاب -O0سوئیچ ها میز های پرتاب در x86-64 هستند و زنجیره ها را در AArch64 مقایسه کنید- .
digestسوئیچ یک میز پرتاب است، سپس یک میز جستجو با فاکتور 16 خم شده، سپس یک میز پرتاب با چهار هدف متفاوت در x86-64 و یک درخت مقایسه با آزمون محدوده 9 تا 10 در AArch64 است -fno-jump-tablesدرختان مقایسه تولید می کند و 16 موردdispatchاهداف خود را از نقل مکان در x86-64 و از یک جدول بایت فشرده در AArch64 می خواند- حلقه ویکتور 4 خط، 2 مخزن، 8 عنصر در هر تکرار، ماسک، محافظ، کاهش و حلقه باقی مانده را گزارش می دهد
- حلقه های ثابت سفر کاملاً متراکی شده یا کاملاً باز شده اند و
-fno-vectorizeباز کردن تا 8 -O1هیچ ویکتوریزیشن ندارد.-O2و-O3در x86-64 همسان هستند و در AArch64 تنها تفاوت دارندcall_switch، کجا-O317 تا دستورات داره و-O218 تا-fno-builtinو به یک حلقه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغازه مغmemcpyتماس- تماس های عقب،
memsetجایگزین، نسخه 16 بایت این خط و-O0دو حرکت کپی شناخته شده است - اون خط خطي که در آن قرار داره از
-O1با شکل بدن - کد را به انتخاب می کند
a > b ? a : b،-x < 0 ? x : -x،(x > 0)وsum % 10 < 5 ? sum % 10 : 5 - گزارش محافظ سکه
%fs:0x28، سوراخ و__stack_chk_failوendbr64در مورد هر عملکرد ساخت CET گزارش می شود - پارسر منبع پارامترهای شاخص عملکرد، موارد سوئیچ، تماس ها و نام های akkumulator را می خواند
- هر زبان در هر ساخت تایید شده و نوع ماتریس ها مطابقت دارند
- این ارقام تعاملی همان داده هایی را که تجزیه و تحلیل تولید می کند، در خود جای می دهد.
روش های شکست تولید
| Failure | What the weak tool reports | What the analysis must prove |
|---|---|---|
| Multiply by a large odd constant | "multiplies by 1717986919" | The constant is ceil(2^s / d) for one d, the error satisfies the bound, and the shift is the smallest valid one |
| Add fix-up after a 32-bit shift | Five arithmetic instructions | The multiplier is 33 bits wide and the tail is the add form of the same method |
| Same multiplier, different shift | The same divisor | The shift selects the divisor: 34 is 10, 33 is 5 |
| Power-of-two signed modulo | "adds 7, masks, subtracts" | The bias rounds toward zero and the result is x % 8 |
Indexed load from .rodata | "a jump table" | A lookup table of values, with entries read from the bytes at addend plus 4 |
Affine arithmetic and a cmov | "computes 10k + 10" | A switch lowered as a linear map, verified against every source case |
| Compare chain with ordered splits | "a sequence of ifs" | A binary search over the case constants with the pivots named |
| Backward branch to a default block | "a loop" | The target leaves the region with an unconditional jump; it is a shared tail |
| Two back edges in one function | "two loops" | One source loop: the vector body and its scalar remainder |
jmp memset@PLT with no ret | "the function has no return" | A tail call that returns through the callee, replacing a store loop |
| Callee missing from the call graph | "never called" | Inlined: the callee's body shape appears in the caller |
%fs:0x28 load and compare | "reads thread-local data" | The stack protector canary, with the fail path to __stack_chk_fail |
| Four odd bytes at function start | "data before the function" | endbr64, the CET landing pad |
| Decompiler shows raw arithmetic | "the tool is wrong" | The tool did not apply a fold; the derivation gives the source form and the quoted output records the gap |
قاعده عملیاتی
شکل را مطابقت دهید، مشتق را به عقب اجرا کنید و ثابتاتی را بنویسید که آن را اثبات می کند. هر زبان را با آدرس های شواهد و فرم منبع رمزگذاری شده اش گزارش دهید، هر یک را با منبع که شما جمع آوری کرده اید تأیید کنید و نقل قول کنید که هر رمزنگاری کننده برای همان بایت ها چاپ کرده است.
خواندن بیشتر
- Granlund, Montgomery, "Division by Invariant Integers using Multiplication" (PLDI 1994)
- Warren, Hacker's Delight, 2nd edition, Chapter 10: Integer Division By Constants
- Lemire, Kaser, Kurz, "Faster Remainder by Direct Computation" (2019)
- libdivide, header-only division by runtime constants
- LLVM
DivisionByConstantInfo.cpp - LLVM
SwitchLoweringUtils.cppوTargetLoweringBase.cpp - LLVM
SimplifyCFG.cpp,simplifySwitchLookup - LLVM Auto-Vectorization documentationوLLVM 3.4 release notes
- LLVM
LoopIdiomRecognize.cpp - LLVM
X86CmovConversion.cpp - LLVM
StackProtector.cppوX86ISelLoweringCall.cpp,getIRStackGuard - glibc
sysdeps/x86_64/nptl/tls.h,tcbhead_t - GCC
tree-switch-conversion.cc،GCC Optimize OptionsوGCC 12 release notes - Ghidra decompiler
ruleaction.cc - angr decompiler documentation
- radare2 book: decompilers
- Compiler Explorer API
- Godbolt, "Optimizations in C++ Compilers: A practical journey", ACM Queue 17(5), 2019
- Microsoft,
/GS(Buffer Security Check)
This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.
Browse the complete course catalog or open this lesson on GitHub.