د کمپیلر رامینځته کول خورا ستونزمن کار دی. مګر، خوشبختانه، د LLVM په څیر د پروژو په پراختیا سره، د دې ستونزې حل خورا ساده شوی، کوم چې حتی یو واحد پروګرامر ته اجازه ورکوي چې یوه نوې ژبه رامینځته کړي چې د C فعالیت سره نږدې وي. د 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
. لارښوونې خورا غیر معمولي دي او ممکن د پوهیدو لپاره یو څه وخت ونیسي. دغه په یاد ولره 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 اصلاح کولو پاس چلول بلل کیږي 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 کاروئ؟
سرچینه: www.habr.com