LLVM kutoka kwa mtazamo wa Go

Kuendeleza mkusanyaji ni kazi ngumu sana. Lakini, kwa bahati nzuri, pamoja na maendeleo ya miradi kama LLVM, suluhu ya tatizo hili imerahisishwa sana, ambayo inaruhusu hata mtayarishaji programu mmoja kuunda lugha mpya ambayo iko karibu na utendaji wa C. Kufanya kazi na LLVM ni ngumu na ukweli kwamba hii mfumo unawakilishwa na idadi kubwa ya nambari, iliyo na nyaraka ndogo. Ili kujaribu kurekebisha kasoro hii, mwandishi wa nyenzo, tafsiri ambayo tunachapisha leo, ataonyesha mifano ya msimbo ulioandikwa katika Go na kuonyesha jinsi zinavyotafsiriwa kwa mara ya kwanza. Nenda kwa SSA, na kisha katika LLVM IR kwa kutumia mkusanyaji vidogoGO. Msimbo wa Go SSA na LLVM IR umehaririwa kidogo ili kuondoa mambo ambayo hayahusiani na maelezo yaliyotolewa hapa, ili kufanya maelezo kueleweka zaidi.

LLVM kutoka kwa mtazamo wa Go

Mfano wa kwanza

Kazi ya kwanza nitakayoiangalia hapa ni utaratibu rahisi wa kuongeza nambari:

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

Kazi hii ni rahisi sana, na, labda, hakuna kitu kinachoweza kuwa rahisi zaidi. Inatafsiriwa kwa nambari ifuatayo ya Go SSA:

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

Kwa mtazamo huu, vidokezo vya aina ya data vimewekwa upande wa kulia na vinaweza kupuuzwa katika hali nyingi.

Mfano huu mdogo tayari hukuruhusu kuona kiini cha kipengele kimoja cha SSA. Yaani, wakati wa kubadilisha msimbo kuwa fomu ya SSA, kila usemi umegawanywa katika sehemu za kimsingi ambazo zimeundwa. Kwa upande wetu, amri return a + b, kwa kweli, inawakilisha shughuli mbili: kuongeza nambari mbili na kurudisha matokeo.

Kwa kuongezea, hapa unaweza kuona vizuizi vya msingi vya programu; katika nambari hii kuna kizuizi kimoja tu - kizuizi cha kuingia. Tutazungumza zaidi kuhusu vitalu hapa chini.

Nambari ya Go SSA inabadilika kwa urahisi kuwa LLVM IR:

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

Unachoweza kugundua ni kwamba ingawa miundo tofauti ya kisintaksia inatumika hapa, muundo wa kazi kimsingi haujabadilika. Nambari ya LLVM IR ina nguvu kidogo kuliko msimbo wa Go SSA, sawa na C. Hapa, katika tamko la kazi, kwanza kuna maelezo ya aina ya data ambayo inarudi, aina ya hoja imeonyeshwa kabla ya jina la hoja. Kwa kuongeza, ili kurahisisha uchanganuzi wa IR, majina ya vyombo vya kimataifa hutanguliwa na ishara @, na kabla ya majina ya mitaa kuna ishara % (tendo la kukokotoa pia linazingatiwa kuwa huluki ya kimataifa).

Jambo moja la kuzingatia kuhusu nambari hii ni kwamba uamuzi wa uwakilishi wa aina ya Go int, ambayo inaweza kuwakilishwa kama thamani ya 32-bit au 64-bit, kulingana na mkusanyaji na lengo la mkusanyiko, inakubaliwa LLVM inapozalisha msimbo wa IR. Hii ni mojawapo ya sababu nyingi ambazo msimbo wa LLVM IR sio, kama watu wengi wanavyofikiri, jukwaa huru. Nambari kama hiyo, iliyoundwa kwa jukwaa moja, haiwezi kuchukuliwa tu na kukusanywa kwa jukwaa lingine (isipokuwa kama unafaa kwa kutatua shida hii. kwa uangalifu uliokithiri).

Jambo lingine la kuvutia ambalo linapaswa kuzingatiwa ni aina i64 si nambari kamili iliyotiwa sahihi: haina upande wowote katika suala la kuwakilisha ishara ya nambari. Kulingana na maagizo, inaweza kuwakilisha nambari zote zilizosainiwa na ambazo hazijasainiwa. Katika kesi ya uwakilishi wa operesheni ya kuongeza, hii haijalishi, kwa hiyo hakuna tofauti katika kufanya kazi na nambari zilizosainiwa au zisizoandikwa. Hapa ningependa kutambua kuwa katika lugha ya C, kufurika kwa nambari kamili iliyotiwa saini husababisha tabia isiyofafanuliwa, kwa hivyo eneo la mbele la Clang linaongeza bendera kwenye operesheni. nsw (hakuna kifurushi kilichotiwa saini), ambayo inaambia LLVM kuwa inaweza kudhani kuwa nyongeza haifuriki.

Hii inaweza kuwa muhimu kwa uboreshaji fulani. Kwa mfano, kuongeza maadili mawili i16 kwenye jukwaa la 32-bit (na rejista 32-bit) inahitaji, baada ya kuongeza, operesheni ya upanuzi wa ishara ili kubaki katika safu. i16. Kwa sababu hii, mara nyingi ni bora zaidi kufanya shughuli kamili kulingana na ukubwa wa rejista ya mashine.

Kinachofuata baada ya msimbo huu wa IR si ya manufaa kwetu sasa. Nambari hiyo imeboreshwa (lakini kwa mfano rahisi kama wetu, hakuna kitu kinachoboreshwa) na kisha kubadilishwa kuwa nambari ya mashine.

Mfano wa pili

Mfano unaofuata tutauangalia utakuwa mgumu zaidi. Yaani, tunazungumza juu ya chaguo la kukokotoa ambalo linajumlisha kipande cha nambari kamili:

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

Nambari hii inabadilishwa kuwa nambari ifuatayo ya 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

Hapa unaweza tayari kuona miundo zaidi ya kawaida ya kuwakilisha msimbo katika fomu ya SSA. Labda kipengele dhahiri zaidi cha nambari hii ni ukweli kwamba hakuna amri za udhibiti wa mtiririko. Ili kudhibiti mtiririko wa mahesabu, kuna kuruka kwa masharti tu na bila masharti, na, ikiwa tunazingatia amri hii kama amri ya kudhibiti mtiririko, amri ya kurudi.

Kwa kweli, hapa unaweza kuzingatia ukweli kwamba programu haijagawanywa katika vitalu kwa kutumia braces curly (kama katika familia C ya lugha). Imegawanywa na maandiko, kukumbusha lugha za mkutano, na kuwasilishwa kwa namna ya vitalu vya msingi. Katika SSA, vizuizi vya msingi vinafafanuliwa kama mfuatano wa msimbo unaoanza na lebo na kumalizia na maagizo ya msingi ya kukamilisha block, kama vile - return ΠΈ jump.

Maelezo mengine ya kuvutia ya nambari hii inawakilishwa na maagizo phi. Maagizo si ya kawaida kabisa na inaweza kuchukua muda kuelewa. kumbuka, hiyo SSA ni kifupi cha Utekelezaji Uliotulia wa Moja. Huu ni uwakilishi wa kati wa msimbo unaotumiwa na wakusanyaji, ambapo kila kigezo hupewa thamani mara moja tu. Hii ni nzuri kwa kuelezea utendaji rahisi kama utendaji wetu myAddiliyoonyeshwa hapo juu, lakini haifai kwa vitendaji ngumu zaidi kama vile chaguo la kukokotoa lililojadiliwa katika sehemu hii sum. Hasa, vigezo vinabadilika wakati wa utekelezaji wa kitanzi i ΠΈ n.

SSA hupita kizuizi cha kugawa maadili tofauti mara moja kwa kutumia kinachojulikana maagizo phi (jina lake limechukuliwa kutoka kwa alfabeti ya Kigiriki). Ukweli ni kwamba ili uwakilishi wa nambari ya SSA itolewe kwa lugha kama C, lazima ubadilishe hila kadhaa. Matokeo ya kuita maagizo haya ni dhamana ya sasa ya kutofautisha (i au n), na orodha ya vizuizi vya msingi hutumiwa kama vigezo vyake. Kwa mfano, fikiria maagizo haya:

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

Maana yake ni kama ifuatavyo: ikiwa kizuizi cha msingi cha hapo awali kilikuwa kizuizi entry (pembejeo), basi t0 ni ya kudumu 0, na ikiwa kizuizi cha msingi cha hapo awali kilikuwa for.body, basi unahitaji kuchukua thamani t6 kutoka kwa kizuizi hiki. Hii yote inaweza kuonekana kuwa ya kushangaza, lakini utaratibu huu ndio hufanya SSA kufanya kazi. Kwa mtazamo wa kibinadamu, hii yote hufanya msimbo kuwa mgumu kuelewa, lakini ukweli kwamba kila thamani imepewa mara moja tu hurahisisha uboreshaji mwingi.

Kumbuka kuwa ukiandika mkusanyaji wako mwenyewe, kwa kawaida hautalazimika kushughulika na aina hii ya vitu. Hata Clang haitoi maagizo haya yote phi, hutumia utaratibu alloca (inafanana na kufanya kazi na anuwai za kawaida za kawaida). Halafu, wakati wa kuendesha pasi ya uboreshaji ya LLVM inaitwa mem2 reg, maagizo alloca imebadilishwa kuwa fomu ya SSA. TinyGo, hata hivyo, inapokea ingizo kutoka kwa Go SSA, ambayo, kwa urahisi, tayari imebadilishwa kuwa fomu ya SSA.

Ubunifu mwingine wa kipande cha msimbo wa kati unaozingatiwa ni kwamba ufikiaji wa vipengele vya kipande kwa index unawakilishwa katika mfumo wa uendeshaji wa kuhesabu anwani na uendeshaji wa kufuta pointer inayosababisha. Hapa unaweza kuona nyongeza ya moja kwa moja ya nambari kwa nambari ya IR (kwa mfano - 1:int) Katika mfano na kazi myAdd hii haijatumika. Sasa kwa kuwa tumeondoa vipengele hivyo, hebu tuangalie nambari hii inakuwa nini inapobadilishwa kuwa fomu ya 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
}

Hapa, kama hapo awali, tunaweza kuona muundo sawa, unaojumuisha miundo mingine ya kisintaksia. Kwa mfano, katika simu phi maadili na lebo zimebadilishwa. Walakini, kuna kitu hapa ambacho kinafaa kulipa kipaumbele maalum.

Kuanza, hapa unaweza kuona saini ya kazi tofauti kabisa. LLVM haitumii vipande, na kwa sababu hiyo, kama uboreshaji, kikusanyaji cha TinyGo kilichozalisha msimbo huu wa kati kiligawanya maelezo ya muundo huu wa data katika sehemu. Inaweza kuwakilisha vitu vitatu vya kipande (ptr, len ΠΈ cap) kama muundo (muundo), lakini kuziwakilisha kama vyombo vitatu tofauti huruhusu uboreshaji fulani. Wakusanyaji wengine wanaweza kuwakilisha kipande kwa njia zingine, kulingana na kanuni za kupiga simu za kazi za jukwaa lengwa.

Kipengele kingine cha kuvutia cha kanuni hii ni matumizi ya maagizo getelementptr (mara nyingi hufupishwa kama GEP).

Maagizo haya hufanya kazi na viashiria na hutumiwa kupata pointer kwa kipengele cha kipande. Kwa mfano, wacha tuilinganishe na nambari ifuatayo iliyoandikwa katika C:

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

Au na yafuatayo sawa na hii:

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

Jambo kuu hapa ni maagizo getelementptr haifanyi shughuli za kukagua. Inahesabu tu pointer mpya kulingana na ile iliyopo. Inaweza kuchukuliwa kama maagizo mul ΠΈ add katika ngazi ya vifaa. Unaweza kusoma zaidi kuhusu maagizo ya GEP hapa.

Kipengele kingine cha kuvutia cha nambari hii ya kati ni matumizi ya maagizo icmp. Haya ni maagizo ya madhumuni ya jumla yanayotumiwa kutekeleza ulinganisho kamili. Matokeo ya maagizo haya daima ni thamani ya aina i1 - thamani ya kimantiki. Katika kesi hii, kulinganisha hufanywa kwa kutumia neno kuu slt (iliyotiwa saini chini ya), kwa kuwa tunalinganisha nambari mbili zilizowakilishwa hapo awali na aina int. Ikiwa tulikuwa tunalinganisha nambari mbili kamili ambazo hazijasainiwa, basi tungetumia icmp, na neno kuu lililotumiwa katika kulinganisha lingekuwa ult. Ili kulinganisha nambari za sehemu zinazoelea, maagizo mengine hutumiwa, fcmp, ambayo inafanya kazi kwa njia sawa.

Matokeo ya

Ninaamini kuwa katika nyenzo hii nimeshughulikia vipengele muhimu zaidi vya LLVM IR. Bila shaka, kuna mengi zaidi hapa. Hasa, uwakilishi wa kati wa msimbo unaweza kuwa na vidokezo vingi vinavyoruhusu uboreshaji kupita kuzingatia vipengele fulani vya msimbo unaojulikana kwa mkusanyaji ambao hauwezi kuonyeshwa vinginevyo katika IR. Kwa mfano, hii ni bendera inbounds Maagizo ya GEP, au bendera nsw ΠΈ nuw, ambayo inaweza kuongezwa kwa maagizo add. Vile vile huenda kwa neno kuu private, ikionyesha kiboreshaji kwamba kazi inayotia alama haitarejelewa kutoka nje ya kitengo cha mkusanyo cha sasa. Hii inaruhusu uboreshaji mwingi wa kuvutia wa kiutaratibu kama kuondoa hoja ambazo hazijatumika.

Unaweza kusoma zaidi kuhusu LLVM ndani nyaraka, ambayo utarejelea mara nyingi wakati wa kuunda mkusanyaji wako mwenyewe wa msingi wa LLVM. Hapa mwongozo, ambayo inaangalia kukuza mkusanyaji kwa lugha rahisi sana. Vyanzo vyote viwili vya habari vitakuwa na manufaa kwako wakati wa kuunda mkusanyaji wako mwenyewe.

Ndugu wasomaji! Je, unatumia LLVM?

LLVM kutoka kwa mtazamo wa Go

Chanzo: mapenzi.com

Kuongeza maoni