LLVM ji perspektîfek Go

Pêşxistina berhevkar karekî pir dijwar e. Lê, bextewar, bi pêşkeftina projeyên mîna LLVM re, çareseriya vê pirsgirêkê pir hêsan e, ku destûrê dide bernamenûsek tenê ku zimanek nû ku di performansê de nêzî C ye biafirîne. Karkirina bi LLVM re ji ber vê yekê tevlihev e ku ev pergal ji hêla hejmareke mezin a kodê ve, bi belgeyên piçûk ve tê destnîşan kirin. Ji bo ku hewl bide ku vê kêmasiyê rast bike, nivîskarê materyalê ku wergera wê îro em diweşînin, dê nimûneyên kodên ku di Go-yê de hatine nivîsandin nîşan bide û nîşan bide ka ew çawa yekem car têne wergerandin. Biçe SSA, û paşê di LLVM IR de berhevkarê bikar tîne tinyGO. Koda Go SSA û LLVM IR hinekî hate guheztin da ku tiştên ku bi ravekirinên ku li vir hatine dayîn re têkildar nînin jêbirin, da ku ravekirin bêtir fêm bikin.

LLVM ji perspektîfek Go

Mînaka yekem

Fonksiyona yekem ku ez ê li vir lê binihêrim mekanîzmayek hêsan e ji bo lê zêdekirina hejmaran:

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

Ev fonksiyon pir hêsan e, û dibe ku, tiştek ne hêsan be. Ew di koda Go SSA ya jêrîn de tê wergerandin:

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

Bi vê dîtinê re, îşaretên celebê daneyê li rastê têne danîn û di pir rewşan de dikarin werin paşguh kirin.

Ev mînaka piçûk jixwe dihêle hûn cewhera yek aliyek SSA bibînin. Ango, dema ku kodê di forma SSA-yê de vediguhezîne, her biwêj di beşên herî bingehîn ên ku ji wan pêk tê tê dabeş kirin. Di rewşa me de, ferman return a + b, di rastiyê de, du operasyonan nîşan dide: du hejmaran zêde bikin û encamê vegerînin.

Wekî din, li vir hûn dikarin blokên bingehîn ên bernameyê bibînin; di vê kodê de tenê yek blok heye - bloka têketinê. Em ê li jêr bêtir li ser blokan biaxivin.

Koda Go SSA bi hêsanî vediguhere LLVM IR:

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

Tiştê ku hûn dikarin bala xwe bidin ev e ku her çend strukturên hevoksaziyê yên cihêreng li vir têne bikar anîn jî, avahiya fonksiyonê bi bingehîn nayê guhertin. Koda LLVM IR ji koda Go SSA-yê piçekî bihêztir e, dişibihe C. Li vir, di danezana fonksiyonê de, pêşî danasîna celebê daneya ku vedigere heye, celebê argumanê berî navê argumanê tê destnîşan kirin. Wekî din, ji bo hêsankirina parskirina IR, navên saziyên gerdûnî bi sembolê têne pêş @, û berî navên herêmî sembolek heye % (fonksîyonek jî saziyek gerdûnî tê hesibandin).

Tiştek ku divê di derbarê vê kodê de were destnîşan kirin ev e ku biryara nûneriya celebê Go ye int, ku dikare wekî nirxek 32-bit an 64-bit were temsîl kirin, li gorî berhevkar û armanca berhevokê ve girêdayî ye, dema ku LLVM koda IR-ê çêdike tê pejirandin. Ev yek ji gelek sedemên ku koda LLVM IR ne, wekî ku pir kes difikirin, platformek serbixwe ye. Kodek wusa, ku ji bo platformek hatî afirandin, bi tenê nikare ji bo platformek din were girtin û berhev kirin (heya ku hûn ji bo çareserkirina vê pirsgirêkê maqûl bin bi lênêrîna zêde).

Xaleke din a balkêş ku hêjayî gotinê ye ew celeb e i64 ne jimareyek bi îmzakirî ye: di warê temsîlkirina nîşana hejmarê de bêalî ye. Bi talîmatê ve girêdayî, ew dikare hem hejmarên îmzekirî û hem jî yên bêîmze nîşan bide. Di mijara temsîlkirina operasyona lêzêdekirinê de, ev ne girîng e, ji ber vê yekê di xebata bi hejmarên îmzekirî an bênîşan de cûdahî tune. Li vir ez dixwazim bibînim ku di zimanê C de, guhêrbarek bêhejmar nîşankirî berbi tevgerek nediyar ve diçe, ji ber vê yekê pêşiya Clang alekê li operasyonê zêde dike. nsw (tu pêça îmzekirî tune), ku ji LLVM re dibêje ku ew dikare bihesibîne ku lêzêdekirin çu carî zêde nabe.

Ev dibe ku ji bo hin optimization girîng be. Ji bo nimûne, du nirxan zêde bikin i16 li ser platformek 32-bit (bi tomarên 32-bit) hewce dike ku, piştî zêdekirinê, operasyonek berfirehkirina nîşanê ji bo ku di nav rêzê de bimîne i16. Ji ber vê yekê, pir caran bikêrhatîtir e ku meriv operasyonên yekjimar li ser bingeha mezinahiyên qeyda makîneyê pêk bîne.

Tiştê ku paşê bi vê koda IR-ê re diqewime, naha ji me re ne eleqedariyek taybetî ye. Kod xweşbîn e (lê di mînakek hêsan a mîna ya me de, tiştek nayê xweşbîn kirin) û dûv re vediguhere koda makîneyê.

Mînaka duyemîn

Mînaka din a ku em ê lê binerin dê hinekî tevlihevtir be. Ango, em li ser fonksiyonek diaxivin ku perçeyek ji jimareyan berhev dike:

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

Ev kod vediguhere koda Go SSA ya jêrîn:

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

Li vir hûn dikarin jixwe bêtir avahiyên tîpîk ên ji bo temsîlkirina kodê di forma SSA de bibînin. Dibe ku taybetmendiya herî eşkere ya vê kodê ev e ku tu fermanên kontrolkirina herikîna birêkûpêk tune. Ji bo kontrolkirina herikîna hesaban, tenê bazdanên şert û bê şert hene, û heke em vê fermanê wekî fermanek ji bo kontrolkirina herikê bihesibînin, fermanek vegerê ye.

Di rastiyê de, li vir hûn dikarin bala xwe bidin vê yekê ku bername bi karanîna pêlavên çîçek (wek malbata zimanan C) di blokan de nayê dabeş kirin. Ew ji hêla etîketan ve tê dabeş kirin, ku zimanên civînê tîne bîra xwe, û di forma blokên bingehîn de têne pêşkêş kirin. Di SSA de, blokên bingehîn wekî rêzikên hevgirtî yên kodê têne destnîşankirin ku bi etîketekê dest pê dikin û bi rêwerzên temamkirina bloka bingehîn diqedin, wek - return и jump.

Detayek din a balkêş a vê kodê ji hêla rêwerzê ve tê destnîşan kirin phi. Rêbernameyên pir neasayî ne û dibe ku hin dem bigire ku meriv fêm bike. bi bîr bîne, ku S.S.A. kurteya Static Single Assignment e. Ev temsîla navbirî ya koda ku ji hêla berhevkeran ve hatî bikar anîn e, ku tê de her guhêrbar tenê carekê nirxek tê destnîşan kirin. Ev ji bo îfadekirina fonksiyonên hêsan ên mîna fonksiyona me pir girîng e myAddli jor hatî destnîşan kirin, lê ji bo fonksiyonên tevlihevtir ên wekî fonksiyona ku di vê beşê de hatî nîqaş kirin ne minasib e sum. Bi taybetî, guhêrbar di dema pêkanîna lûkê de diguhezin i и n.

SSA yek carî bi karanîna rêwerzek ku jê re tê gotin tixûbdarkirina nirxên guhêrbar derbas dike. phi (navê wê ji alfabeya yewnanî hatiye girtin). Rastî ev e ku ji bo ku nûnertiya koda SSA ji bo zimanên wekî C were çêkirin, divê hûn serî li hin hîleyan bidin. Encama bangkirina vê talîmatê nirxa heyî ya guhêrbar e (i an n), û navnîşek blokên bingehîn wekî pîvanên wê tê bikar anîn. Mînakî, vê rêwerzê bifikirin:

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

Wateya wê wiha ye: heke bloka bingehîn a berê blokek bûya entry (input), paşê t0 domdar e 0, û heger bloka bingehîn ya berê bû for.body, hingê hûn hewce ne ku nirxê bigirin t6 ji vê blokê. Dibe ku ev hemî pir nepenî xuya bike, lê ev mekanîzma ew e ku SSA dixebitîne. Ji perspektîfa mirovî ve, ev hemî fêmkirina kodê dijwar dike, lê rastiya ku her nirx tenê carekê tête destnîşan kirin gelek xweşbîniyan pir hêsantir dike.

Hişyar bikin ku heke hûn berhevkarê xwe binivîsin, hûn ê bi gelemperî ne hewce ne ku bi vî rengî re mijûl bibin. Tewra Clang van hemî rêwerzan çênake phi, ew mekanîzmayek bikar tîne alloca (ew dişibe xebata bi guhêrbarên herêmî yên asayî). Dûv re, dema ku rêça optîmîzasyona LLVM tê gotin mem2reg, talîmatên alloca veguherandin forma SSA. Lêbelê, TinyGo ji Go SSA-yê têketinê distîne, ku, bi hêsanî, jixwe veguherî forma SSA.

Nûbûnek din a perçeya koda navîn a ku li ber çavan tê girtin ev e ku gihandina hêmanên perçeyê ji hêla îndeksê ve di forma operasyonek hesabkirina navnîşan û operasyona veqetandina nîşana encam de tê destnîşan kirin. Li vir hûn dikarin lêzêdekirina rasterast li koda IR-ê bibînin (mînak - 1:int). Di mînaka bi fonksiyonê myAdd ev nehatiye bikaranîn. Naha ku me wan taybetmendî ji rê derxistiye, ka em binihêrin ka ev kod dema ku di forma LLVM IR-ê de tê veguheztin çi dibe:

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
}

Li vir, wekî berê, em dikarin heman avahiyê bibînin, ku tê de avahiyên hevoksaziyê yên din jî hene. Mînakî, di bangan de phi nirx û etîket hatin guheztin. Lêbelê, li vir tiştek heye ku hêja ye ku meriv bi taybetî bala xwe bide ser.

Ji bo destpêkê, li vir hûn dikarin nîşanek fonksiyonek bi tevahî cûda bibînin. LLVM perçeyan piştgirî nake, û di encamê de, wekî optimîzasyonek, berhevkara TinyGo ya ku ev koda navîn çêkiriye danasîna vê avahiya daneyê li beşan parçe dike. Ew dikare sê hêmanên perçeyê temsîl bike (ptr, len и cap) wekî avahiyek (struktur), lê temsîlkirina wan wekî sê hebûnên cihêreng destûrê dide hin xweşbîniyan. Berhevkarên din dikarin perçeyê bi awayên din temsîl bikin, li gorî peymanên bangê yên fonksiyonên platforma armanc.

Taybetmendiyek din a balkêş a vê kodê karanîna rêwerzê ye getelementptr (pir caran wekî GEP tê kurt kirin).

Ev talîmat bi nîşankeran re dixebite û ji bo bidestxistina nîşanek ji hêmanek perçeyê re tê bikar anîn. Mînakî, em wê bi koda jêrîn a ku di C-yê de hatî nivîsandin berhev bikin:

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

An jî bi wekheviya jêrîn a vê yekê:

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

Li vir tiştê herî girîng ew e ku rêwerz e getelementptr operasyonên dereferanskirinê nake. Ew tenê nîşanek nû li ser bingeha heyî hesab dike. Ew dikare wekî talîmatan were girtin mul и add di asta hardware. Hûn dikarin li ser rêwerzên GEP-ê bêtir bixwînin vir.

Taybetmendiyek din a balkêş a vê koda navîn karanîna rêwerzê ye icmp. Ev rêwerzek armancek gelemperî ye ku ji bo pêkanîna berhevokên jimare tê bikar anîn. Encama vê talîmatê her gav nirxek celeb e i1 - nirxa mantiqî. Di vê rewşê de, berhevokek bi karanîna keywordê tê çêkirin slt (kêmtir hatiye îmzekirin), ji ber ku em du hejmarên ku berê ji hêla celebê ve hatine destnîşan kirin berhev dikin int. Ger me du hejmarên bênîşan bidin ber hev, wê hingê em ê bikar bînin icmp, û peyva sereke ya ku di berhevdanê de tê bikar anîn dê bibe ult. Ji bo berawirdkirina hejmarên xala herikînê, rêwerzek din tê bikar anîn, fcmp, ku bi heman rengî dixebite.

Encam

Ez bawer dikim ku di vê materyalê de min taybetmendiyên herî girîng ên LLVM IR veşartiye. Bê guman, li vir pir zêde ye. Bi taybetî, nûneriya navberê ya kodê dibe ku gelek şiroveyan bihewîne ku rê dide derbasbûnên xweşbîniyê ku hin taybetmendiyên kodê yên ku ji berhevkerê re têne zanîn ku wekî din di IR-ê de nayên diyar kirin li ber çavan bigirin. Mînak ev ala ye inbounds talîmatên GEP, an alên nsw и nuw, ku dikare li talîmatan were zêdekirin add. Heman tişt ji bo keyword jî derbas dibe private, ji optimîzatorê re destnîşan dike ku fonksiyona ku ew nîşan dide dê ji derveyî yekîneya berhevkirina heyî neyê referans kirin. Ev rê dide gelek optimîzasyonên navproceduralî yên balkêş ên mîna rakirina argumanên neyên bikar anîn.

Hûn dikarin di derbarê LLVM de bêtir bixwînin belgekirin, ya ku hûn ê pir caran gava ku berhevkar-based LLVM-ya xwe pêşve bixin nav lê bikin. Vir birêvebirî, ku li pêşxistina berhevkarek ji bo zimanek pir hêsan dinêre. Van her du çavkaniyên agahdarî dema ku berhevkarê xwe biafirînin dê ji we re bikêr bin.

Xwendevanên delal! Ma hûn LLVM bikar tînin?

LLVM ji perspektîfek Go

Source: www.habr.com

Add a comment