LLVM د تګ لید څخه

د کمپیلر رامینځته کول خورا ستونزمن کار دی. مګر، خوشبختانه، د LLVM په څیر د پروژو په پراختیا سره، د دې ستونزې حل خورا ساده شوی، کوم چې حتی یو واحد پروګرامر ته اجازه ورکوي چې یوه نوې ژبه رامینځته کړي چې د C فعالیت سره نږدې وي. د LLVM سره کار کول د دې حقیقت له امله پیچلي دي. سیسټم د لوی مقدار کوډ لخوا نمایش کیږي، د لږو اسنادو سره سمبال شوی. د دې نیمګړتیا د سمولو هڅه کولو لپاره، د موادو لیکوال، هغه ژباړه چې موږ یې نن خپروو، د ګو کې لیکل شوي کوډ مثالونه وښایه او وښيي چې څنګه دوی په لومړي ځل ژباړل شوي. SSA لاړ شه، او بیا په LLVM IR کې د کمپیلر په کارولو سره ټینیګو. د Go SSA او LLVM IR کوډ یو څه ترمیم شوی ترڅو هغه شیان لرې کړي چې دلته ورکړل شوي توضیحاتو سره تړاو نلري ، ترڅو توضیحات نور پوه شي.

LLVM د تګ لید څخه

لومړی مثال

لومړی فنکشن چې زه یې دلته ګورم د شمیرو اضافه کولو لپاره یو ساده میکانیزم دی:

func myAdd(a, b int) int{
    return a + b
}

دا فعالیت خورا ساده دی، او شاید، هیڅ شی ساده نه وي. دا په لاندې Go SSA کوډ کې ژباړه کوي:

func myAdd(a int, b int) int:
entry:
    t0 = a + b                                                    int
    return t0

د دې نمایندګۍ سره، د فعالیت ډیټا ډول نښې په ښي خوا کې ایښودل شوي او په ډیری قضیو کې له پامه غورځول کیدی شي.

دا کوچنۍ بیلګه لا دمخه تاسو ته اجازه درکوي چې د SSA د یو اړخ جوهر وګورئ. د مثال په توګه، کله چې کوډ د SSA بڼه بدلوي، هر بیان په ډیرو ابتدايي برخو ویشل کیږي چې دا جوړ شوی. زموږ په قضیه کې، امر return a + bپه حقیقت کې، د دوو عملیاتو استازیتوب کوي: دوه شمیرې اضافه کول او پایله بیرته راګرځول.

سربیره پردې ، دلته تاسو د برنامې لومړني بلاکونه لیدلی شئ؛ پدې کوډ کې یوازې یو بلاک شتون لري - د ننوتلو بلاک. موږ به لاندې د بلاکونو په اړه نور خبرې وکړو.

د Go SSA کوډ په اسانۍ سره LLVM IR ته بدلوي:

define i64 @myAdd(i64 %a, i64 %b) {
entry:
  %0 = add i64 %a, %b
  ret i64 %0
}

هغه څه چې تاسو یې لیدلی شئ دا دی چې که څه هم دلته مختلف ترکیبي جوړښتونه کارول کیږي ، د فعالیت جوړښت اساسا بدل شوی نه دی. د LLVM IR کوډ د Go SSA کوډ څخه یو څه پیاوړی دی، د C سره ورته دی. دلته، د فعالیت اعالمیه کې، لومړی د ډیټا ډول تشریح دی چې دا بیرته راګرځي، د دلیل ډول د دلیل نوم څخه مخکې ښودل شوی. برسېره پردې، د IR تحلیل ساده کولو لپاره، د نړیوالو ادارو نومونه د سمبول څخه مخکې دي @، او د ځایی نومونو دمخه یو سمبول شتون لري % (یو فعالیت یو نړیوال وجود هم ګڼل کیږي).

د دې کوډ په اړه د یادولو لپاره یو شی دا دی چې د Go ډول نمایش پریکړه int، کوم چې د 32-bit یا 64-bit ارزښت په توګه ښودل کیدی شي ، د تالیف کونکي او د تالیف هدف پورې اړه لري ، کله چې LLVM IR کوډ رامینځته کوي منل کیږي. دا یو له ډیری دلیلونو څخه دی چې د LLVM IR کوډ ندی، لکه څنګه چې ډیری خلک فکر کوي، پلیټ فارم خپلواک دی. دا ډول کوډ، د یو پلیټ فارم لپاره جوړ شوی، په ساده ډول نشي اخیستل کیدی او د بل پلیټ فارم لپاره تالیف کیدی شي (مګر تاسو د دې ستونزې حل کولو لپاره مناسب نه یاست) په ډیر احتیاط سره).

یو بل په زړه پوری ټکی د یادولو وړ دی چې ډول دی i64 یو لاسلیک شوی عدد ندی: دا د شمیرې نښه نمایندګي کولو له مخې بې طرفه ده. د لارښوونې پورې اړه لري، دا کولی شي دواړه لاسلیک شوي او نه لاسلیک شوي شمیرې استازیتوب وکړي. د اضافي عملیاتو د نمایندګۍ په صورت کې، دا مهمه نده، نو د لاسلیک شوي یا غیر لاسلیک شوي شمیرو سره کار کولو کې هیڅ توپیر شتون نلري. دلته زه غواړم یادونه وکړم چې په C ژبه کې، د لاسلیک شوي انټیجر متغیر څخه تیریدل د نه تعریف شوي چلند لامل کیږي، نو د کلینګ فرنټ اینډ عملیاتو ته بیرغ اضافه کوي nsw (نه لاسلیک شوی لفاف)، کوم چې LLVM ته وایي چې دا فرض کولی شي چې اضافه هیڅکله نه تیریږي.

دا ممکن د ځینې اصلاح کولو لپاره مهم وي. د مثال په توګه، دوه ارزښتونه اضافه کول i16 په 32-bit پلیټ فارم کې (د 32-bit راجسترونو سره) د دې سربیره ، په حد کې پاتې کیدو لپاره د لاسلیک توسیع عملیاتو ته اړتیا لري i16. د دې له امله، دا ډیری وختونه د ماشین د راجستر اندازې پراساس د انټیجر عملیات ترسره کول خورا اغیزمن دي.

د دې IR کوډ سره وروسته څه پیښیږي اوس زموږ لپاره ځانګړې علاقه نلري. کوډ اصلاح شوی دی (مګر زموږ په څیر د ساده مثال په حالت کې ، هیڅ شی نه اصلاح کیږي) او بیا د ماشین کوډ ته بدل شوی.

دوهم مثال

بل مثال چې موږ به یې وګورو یو څه ډیر پیچلي وي. د مثال په توګه، موږ د داسې فنکشن په اړه خبرې کوو چې د انټیجرونو یوه ټوټه راټولوي:

func sum(numbers []int) int {
    n := 0
    for i := 0; i < len(numbers); i++ {
        n += numbers[i]
    }
    return n
}

دا کوډ لاندې Go SSA کوډ ته بدلوي:

func sum(numbers []int) int:
entry:
    jump for.loop
for.loop:
    t0 = phi [entry: 0:int, for.body: t6] #n                       int
    t1 = phi [entry: 0:int, for.body: t7] #i                       int
    t2 = len(numbers)                                              int
    t3 = t1 < t2                                                  bool
    if t3 goto for.body else for.done
for.body:
    t4 = &numbers[t1]                                             *int
    t5 = *t4                                                       int
    t6 = t0 + t5                                                   int
    t7 = t1 + 1:int                                                int
    jump for.loop
for.done:
    return t0

دلته تاسو دمخه د SSA فارم کې د کوډ نمایندګۍ لپاره نور جوړښتونه لیدلی شئ. شاید د دې کوډ ترټولو څرګند ځانګړتیا دا حقیقت دی چې د جریان کنټرول کوم جوړښت شوي حکمونه شتون نلري. د محاسبې جریان کنټرولولو لپاره ، یوازې مشروط او غیر مشروط کودونه شتون لري ، او که موږ دا کمانډ د جریان کنټرول لپاره د کمانډ په توګه وګورو ، د بیرته راستنیدو کمانډ.

په حقیقت کې ، دلته تاسو دې حقیقت ته پاملرنه کولی شئ چې برنامه د منحل منحلاتو په کارولو سره په بلاکونو نه ویشل شوې (لکه څنګه چې د ژبې C کورنۍ کې). دا د لیبلونو په واسطه ویشل شوی، د مجلس ژبو یادونه کوي، او د اساسي بلاکونو په بڼه وړاندې کیږي. په SSA کې، بنسټیز بلاکونه د کوډ د تړلو ترتیبونو په توګه تعریف شوي چې د لیبل سره پیل کیږي او د اساسي بلاک بشپړولو لارښوونو سره پای ته رسیږي، لکه - return и jump.

د دې کوډ بل په زړه پورې توضیحات د لارښوونې لخوا استازیتوب کیږي phi. لارښوونې خورا غیر معمولي دي او ممکن د پوهیدو لپاره یو څه وخت ونیسي. دغه په ​​یاد ولره S.S.A. د جامد واحد دندې لپاره لنډ دی. دا د کوډ منځګړیتوب استازیتوب دی چې د تالیف کونکو لخوا کارول کیږي، په کوم کې چې هر متغیر یوازې یو ځل ارزښت ټاکل شوی. دا زموږ د فعالیت په څیر د ساده کارونو څرګندولو لپاره عالي دی myAddپورته ښودل شوي، مګر د ډیرو پیچلو دندو لپاره مناسب ندي لکه فنکشن چې پدې برخه کې بحث شوی sum. په ځانګړې توګه، متغیرات د لوپ د اجرا کولو په جریان کې بدلیږي i и n.

SSA یوځل د تش په نامه لارښوونې په کارولو سره د متغیر ارزښتونو ټاکلو محدودیت له پامه غورځوي phi (نوم یې د یوناني الفبا څخه اخیستل شوی دی). حقیقت دا دی چې د SSA د کوډ نمایندګۍ لپاره چې د C په څیر ژبو لپاره رامینځته کیږي، تاسو باید ځینې چلونه وکاروئ. د دې لارښوونې زنګ وهلو پایله د متغیر اوسنی ارزښت دی (i او یا n)، او د بنسټیزو بلاکونو لیست د دې پیرامیټونو په توګه کارول کیږي. د مثال په توګه، دا لارښوونې په پام کې ونیسئ:

t0 = phi [entry: 0:int, for.body: t6] #n

د هغې معنی په لاندې ډول ده: که پخوانی بنسټیز بلاک یو بلاک و entry (input)، بیا t0 ثابت دی 0، او که پخوانی بنسټیز بلاک و for.body، بیا تاسو اړتیا لرئ ارزښت واخلئ t6 له دې بلاک څخه. دا ټول ممکن خورا پراسرار ښکاري، مګر دا میکانیزم هغه څه دي چې SSA کار کوي. د انساني لید څخه، دا ټول د کوډ پوهیدل ستونزمن کوي، مګر دا حقیقت چې هر ارزښت یوازې یو ځل ټاکل شوی ډیری اصلاح کول خورا اسانه کوي.

په یاد ولرئ چې که تاسو خپل کمپیلر ولیکئ ، نو تاسو معمولا د دې ډول توکو سره معامله کولو ته اړتیا نلرئ. حتی کلینګ دا ټول لارښوونې نه رامینځته کوي phi، دا یو میکانیزم کاروي alloca (دا د عادي محلي متغیرونو سره کار کولو ته ورته دی). بیا، کله چې د LLVM اصلاح کولو پاس چلول بلل کیږي mem2reg, لارښوونې alloca د SSA فورمې ته بدل شوی. TinyGo، په هرصورت، د Go SSA څخه معلومات ترلاسه کوي، کوم چې، په اسانۍ سره، دمخه د SSA فورمه بدل شوی.

د غور لاندې د منځګړی کوډ د ټوټې یو بل نوښت دا دی چې د شاخص په واسطه سلائس عناصرو ته لاسرسی د پته محاسبه کولو عملیات او د پایله شوي پوائنټر له مینځه وړلو عملیات په توګه ښودل کیږي. دلته تاسو کولی شئ په IR کوډ کې د مستقلاتو مستقیم اضافه وګورئ (د مثال په توګه - 1:int). په مثال کې د فنکشن سره myAdd دا نه دی کارول شوی. اوس چې موږ دا ځانګړتیاوې له لارې لرې کړې، راځئ چې وګورو چې دا کوډ څه کیږي کله چې د LLVM IR فارم ته بدل شي:

define i64 @sum(i64* %ptr, i64 %len, i64 %cap) {
entry:
  br label %for.loop

for.loop:                                         ; preds = %for.body, %entry
  %0 = phi i64 [ 0, %entry ], [ %5, %deref.next ]
  %1 = phi i64 [ 0, %entry ], [ %6, %deref.next ]
  %2 = icmp slt i64 %1, %len
  br i1 %2, label %for.body, label %for.done

for.body:                                         ; preds = %for.loop
  %3 = getelementptr i64, i64* %ptr, i64 %1
  %4 = load i64, i64* %3
  %5 = add i64 %0, %4
  %6 = add i64 %1, 1
  br label %for.loop

for.done:                                         ; preds = %for.loop
  ret i64 %0
}

دلته، د پخوا په څیر، موږ کولی شو ورته جوړښت وګورو، کوم چې نور نحوي جوړښتونه شامل دي. د مثال په توګه، په تلیفونونو کې phi ارزښتونه او لیبلونه بدل شوي. په هرصورت، دلته یو څه شتون لري چې د ځانګړې پاملرنې ارزښت لري.

د پیل کولو لپاره، دلته تاسو کولی شئ د بشپړ مختلف فعالیت لاسلیک وګورئ. LLVM د سلائسونو ملاتړ نه کوي، او د پایلې په توګه، د اصلاح کولو په توګه، د TinyGo کمپیلر چې دا منځګړیتوب کوډ یې رامینځته کړی د دې ډاټا جوړښت توضیحات په برخو ویشي. دا کولی شي د درې ټوټې عناصرو استازیتوب وکړي (ptr, len и cap) د جوړښت (ساختمان) په توګه، مګر د دریو جلا ادارو په توګه د دوی استازیتوب د ځینو اصلاح کولو لپاره اجازه ورکوي. نور تالیف کونکي ممکن په نورو لارو سلائس استازیتوب وکړي ، د هدف پلیټ فارم د دندو د زنګ وهلو کنوانسیونونو پورې اړه لري.

د دې کوډ بله زړه پورې ځانګړتیا د لارښوونې کارول دي getelementptr (اکثرا د GEP په توګه لنډیز شوی).

دا لارښوونه د پوائنټرونو سره کار کوي او د ټوټې عنصر ته د اشارې ترلاسه کولو لپاره کارول کیږي. د مثال په توګه، راځئ چې دا په C کې لیکل شوي لاندې کوډ سره پرتله کړو:

int* sliceptr(int *ptr, int index) {
    return &ptr[index];
}

یا د دې سره د لاندې معادل سره:

int* sliceptr(int *ptr, int index) {
    return ptr + index;
}

دلته ترټولو مهمه خبره دا ده چې لارښوونې getelementptr د حواله کولو عملیات نه ترسره کوي. دا یوازې د موجوده یو پراساس یو نوی پوائنټر محاسبه کوي. دا د لارښوونو په توګه اخیستل کیدی شي mul и add د هارډویر په کچه. تاسو کولی شئ د GEP لارښوونو په اړه نور ولولئ دلته.

د دې منځني کوډ بله په زړه پورې ځانګړتیا د لارښوونې کارول دي icmp. دا د عمومي هدف لارښوونې دي چې د عدد پرتله کولو پلي کولو لپاره کارول کیږي. د دې لارښوونې پایله تل د ډول ارزښت دی i1 - منطقي ارزښت. په دې حالت کې، پرتله کول د کلیدي کلمې په کارولو سره ترسره کیږي slt (په پرتله لږ لاسلیک شوی)، ځکه چې موږ دوه شمیرې پرتله کوو چې مخکې د ډول لخوا استازیتوب شوي int. که موږ دوه غیر لاسلیک شوي عددونه پرتله کړو، نو موږ به کاروو icmp، او په پرتله کولو کې کارول شوي کلیدي کلمې به وي ult. د فلوټینګ پوائنټ شمیرو پرتله کولو لپاره، بله لارښوونه کارول کیږي، fcmp، کوم چې په ورته ډول کار کوي.

پایلې

زه باور لرم چې پدې موادو کې ما د LLVM IR خورا مهم ځانګړتیاوې پوښلي دي. البته، دلته ډیر څه شتون لري. په ځانګړې توګه، د کوډ منځګړیتوب استازیتوب ممکن ډیری تشریحات ولري چې د اصلاح کولو پاسونو ته اجازه ورکوي چې د کوډ ځانګړي ځانګړتیاوې په پام کې ونیسي چې کمپیلر ته پیژندل شوي چې په بل ډول په IR کې نشي څرګندیدلی. د مثال په توګه، دا یو بیرغ دی inbounds د GEP لارښوونې، یا بیرغونه nsw и nuw، کوم چې په لارښوونو کې اضافه کیدی شي add. ورته د کلیدي کلمې لپاره ځي private، اصلاح کونکي ته په ګوته کوي چې هغه فعالیت چې دا یې په نښه کوي به د اوسني تالیف واحد څخه بهر نه راجع کیږي. دا د ډیری په زړه پورې متقابل عمل اصلاح کولو ته اجازه ورکوي لکه د نه کارول شوي دلیلونو له مینځه وړل.

تاسو کولی شئ د LLVM په اړه نور ولولئ in اسناد، کوم چې تاسو به اکثرا ورته اشاره وکړئ کله چې خپل د LLVM پراساس کمپیلر رامینځته کړئ. دلته رهبري، کوم چې د خورا ساده ژبې لپاره د کمپیلر رامینځته کولو ته ګوري. د معلوماتو دا دواړه سرچینې به ستاسو لپاره ګټورې وي کله چې خپل کمپیلر رامینځته کړئ.

ګرانو لوستونکو! ایا تاسو LLVM کاروئ؟

LLVM د تګ لید څخه

سرچینه: www.habr.com

Add a comment