Phase 13: Tools & Protocols

زبان های سازنده: بهینه سازی به کد شما چه می کند و چگونه آن را بازخوردیم

یک سازنده بهینه سازی ساختارهای منبع را با اشکال دستورالعمل ارزان تر جایگزین می کند. هر شکل دارای مشتق است و این درس شناسایی کننده را ایجاد می کند که مشتق را به عقب اجرا می کند و آن را با منبع بررسی می کند.

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, %rax

0x2335ـه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انتخاب محور. چراغ ها هر نتیجه ای را یک تابع دارند.

FunctionSource-O0 x86-64-O2 x86-64-O2 AArch64
linear_switchcases 0 to 6 return 10 to 70jump table, 7 entrieslinear map 10*k + 10, default -1madd and csinv
table_switchcases 0 to 6 return 10 to 60 then 71jump table, 7 entrieslookup table at .rodata+0x28lookup table at .rodata+0x0
call_switchcases 0 to 4 call one of five pointersjump table, 5 entriesjump table, 5 entriescomparison tree, pivot 1
sparse_switchcases 1, 100, 10000, 1000000, 100000000compare chaincomparison tree, pivot 9999comparison tree, pivot 9999
two_casescases 3 and 4 return 7 and 9compare chainselect chain k != 3 ? (k == 4)*9 : 7two 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در این ترتیب:

  1. parse_listing().llvm-objdump -d -rمتن به عملکردها، دستورالعمل ها و نقل مکان های متصل برای هر دو معماری.
  2. ElfFileبخش ها، نمادها و.rela.*نوشته ها وwords()نوشته هاي جدول رو ميخواد
  3. magic_numbers()،divisor_from_magic()وevaluate_division()مشتق گرانلوند-مونتگمری به جلو، عقب و به عنوان دنباله دستورالعمل است.
  4. parse_source()ساختارهای مورد نیاز برای تأیید را از پرونده C استخراج می کند.
  5. Evaluatorاین گذرنامه نمادیه ای بلوک به بلوک است که نام ها را ثبت می کند و اسلات را جمع می کند.
  6. find_divisions()وfind_modulos()با ضرب، تغییر، تنظیم، ضرب و برگشت و قدرت دو شکل مطابقت دارد.
  7. find_switches()جدول های پرتاب، جدول های جستجو، نقشه های خطی، زنجیره های انتخاب، درختان مقایسه و مقایسه زنجیره ها را طبقه بندی می کند.
  8. 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()بقیه رو بپوش
  9. 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داده های موجود را چاپ می کند.

ازش استفاده کن

از شناسه در این ترتیب در یک شی جدید که ساختی استفاده کن.

  1. دستگیرش کنllvm-objdump -d -rاز موضوع و نگه داشتن دقیق کامپایلر فرمان کنار آن. شناسه معماری را از عنوان لیست می خواند.
  2. فرار کنscanدر لیست و مطالعه کنیدunverifiedهر یک یا یک ساختاری است که منبع تجزیه کننده نمی بیند یا یک شکل که شناسه اشتباه خوانده شده است، و هر دو سزاوار نگاهی به آدرس های شواهد هستند.
  3. براي هر بخش، بهشون بخونيدalgorithm. میدانclangMatchesAlgorithmدرست است، کمپایلر کوچکترین تغییر معتبر را انتخاب کرد. وقتی غلط است، کمپایلر دلیل دیگری داشت که هنوز نمی دانید؛ ثابت ها را بنویسید.
  4. برای هر سوئیچ، مقایسه کنیدloweringجدول ای که به یک درخت تبدیل می شود، یا یک درخت که به یک جدول تبدیل می شود، یک آستانه ای را نشان می دهد که می توانید از منبع LLVM نام ببرید.
  5. برای هر حلقه، بخونelementsPerIterationوscalarRemainderLoop، پس تایید کنید دو لبه عقب درس 33 گزارش ها یک حلقه منبع هستند
  6. یک کامپایلر را روی همان شی اجرا کنید، متن آن را در کنار هر لغت نقل قول کنید و آن را که طنین کرده است ثبت کنید. این ضبط خط پایه برای درس 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 را می خواند
  • هر زبان در هر ساخت تایید شده و نوع ماتریس ها مطابقت دارند
  • این ارقام تعاملی همان داده هایی را که تجزیه و تحلیل تولید می کند، در خود جای می دهد.

روش های شکست تولید

FailureWhat the weak tool reportsWhat 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 shiftFive arithmetic instructionsThe multiplier is 33 bits wide and the tail is the add form of the same method
Same multiplier, different shiftThe same divisorThe 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

قاعده عملیاتی

شکل را مطابقت دهید، مشتق را به عقب اجرا کنید و ثابتاتی را بنویسید که آن را اثبات می کند. هر زبان را با آدرس های شواهد و فرم منبع رمزگذاری شده اش گزارش دهید، هر یک را با منبع که شما جمع آوری کرده اید تأیید کنید و نقل قول کنید که هر رمزنگاری کننده برای همان بایت ها چاپ کرده است.

خواندن بیشتر

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.