LLVM аз нуқтаи назари Go

Таҳияи компилятор кори хеле душвор аст. Аммо, хушбахтона, бо таҳияи лоиҳаҳо ба монанди LLVM, ҳалли ин мушкилот хеле содда карда мешавад, ки ҳатто ба як барномасоз имкон медиҳад, ки забони наверо эҷод кунад, ки аз ҷиҳати иҷроиш ба C наздик аст. Кор бо LLVM аз он сабаб мушкил аст, ки ин Система бо миқдори зиёди код, ки бо ҳуҷҷатҳои кам муҷаҳҳаз шудааст, муаррифӣ карда мешавад. Барои ислоҳи ин камбуд, муаллифи мавод, ки тарҷумаи онро имрӯз нашр мекунем, мехоҳад намунаҳои кодҳои дар Go навишташударо намоиш диҳад ва нишон диҳад, ки чӣ гуна онҳо бори аввал ба забони англисӣ тарҷума шудаанд. Ба SSA равед, ва сипас дар LLVM IR бо истифода аз компилятор tinyGO. Рамзи Go SSA ва LLVM IR каме таҳрир карда шудааст, то чизҳое, ки ба тавзеҳоти дар ин ҷо овардашуда мувофиқ нестанд, бо мақсади фаҳмотар шудани тавзеҳот.

LLVM аз нуқтаи назари Go

Мисоли аввал

Аввалин функсияе, ки ман дар ин ҷо мебинам, як механизми оддии илова кардани рақамҳост:

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-бит ё 64-бит муаррифӣ шавад, вобаста ба компилятор ва ҳадафи компиляция, ҳангоми тавлиди LLVM рамзи IR қабул карда мешавад. Ин яке аз сабабҳои зиёди он аст, ки рамзи LLVM IR, тавре ки бисёриҳо фикр мекунанд, аз платформа мустақил нест. Чунин кодеро, ки барои як платформа сохта шудааст, барои платформаи дигар гирифтан ва тартиб додан мумкин нест (агар шумо барои ҳалли ин мушкилот мувофиқ набошед бо эҳтиёти шадид).

Боз як нуктаи ҷолиби диққати он аст, ки навъи i64 адади бутуни имзошуда нест: аз чихати ифодаи аломати адад бетараф аст. Вобаста аз дастур, он метавонад рақамҳои имзошуда ва беимзоро ифода кунад. Дар сурати ифодаи амалиёти изофӣ, ин аҳамият надорад, бинобар ин дар кор бо рақамҳои имзошуда ё беимзо тафовуте вуҷуд надорад. Дар ин ҷо ман мехоҳам қайд намоям, ки дар забони C зиёд кардани як тағирёбандаи бутуни имзошуда ба рафтори номуайян оварда мерасонад, аз ин рӯ фронти Clang ба амалиёт парчам илова мекунад. nsw (ҳеҷ бастабандии имзошуда), ки ба LLVM мегӯяд, ки он метавонад тахмин кунад, ки илова ҳеҷ гоҳ пур намешавад.

Ин метавонад барои баъзе оптимизатсияҳо муҳим бошад. Масалан, илова кардани ду арзиш i16 дар платформаи 32-битӣ (бо регистрҳои 32-битӣ) пас аз илова, амалиёти васеъкунии аломатро талаб мекунад, то дар диапазон боқӣ монад. 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. Дастурҳо хеле ғайриоддӣ мебошанд ва барои фаҳмидани он шояд чанд вақт лозим шавад. дар хотир доред, ки SSA барои таъини ягонаи статикӣ кӯтоҳ аст. Ин намоиши фосилавии кодест, ки аз ҷониби компиляторҳо истифода мешавад, ки дар он ҳар як тағирёбанда танҳо як маротиба арзиш таъин карда мешавад. Ин барои ифодаи вазифаҳои оддӣ ба монанди функсияи мо бузург аст myAddдар боло нишон дода шудааст, аммо барои вазифаҳои мураккабтар ба монанди функсияе, ки дар ин бахш баррасӣ мешавад, мувофиқ нест sum. Махсусан, тағирёбандаҳо ҳангоми иҷрои давра тағир меёбанд i и n.

SSA маҳдудияти таъини арзишҳои тағирёбандаро як маротиба бо истифода аз дастури номбурда мегузарад phi (номи он аз алифбои юнонӣ гирифта шудааст). Гап дар он аст, ки барои он ки рамзи SSA барои забонҳо ба монанди C тавлид шавад, шумо бояд ба баъзе ҳилаҳо муроҷиат кунед. Натиҷаи даъвати ин дастур арзиши ҷории тағирёбанда (i ё n) ва рӯйхати блокҳои асосӣ ҳамчун параметрҳои он истифода мешавад. Масалан, ин дастурро баррасӣ кунед:

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

Маънои он чунин аст: агар блоки асосии пештара блок бошад entry (ворид), пас t0 доимист 0, ва агар блоки асосии пештара буд for.body, пас шумо бояд арзишро гиред t6 аз ин блок. Ин ҳама метавонад хеле пурасрор ба назар расад, аммо ин механизм он чизест, ки SSA кор кунад. Аз нуқтаи назари инсон, ин ҳама фаҳмидани рамзро душвор мегардонад, аммо он, ки ҳар як арзиш танҳо як маротиба таъин карда мешавад, бисёр оптимизатсияҳоро хеле осон мекунад.

Дар хотир доред, ки агар шумо компилятори шахсии худро нависед, ба шумо одатан лозим нест, ки бо ин гуна мавод сарукор дошта бошед. Ҳатто Clang ҳамаи ин дастурҳоро тавлид намекунад 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 бештар хонед хуччатхо, ки шумо ҳангоми таҳияи компилятори LLVM-и худ аксар вақт ба он муроҷиат мекунед. Ин ҷо роҳнамо, ки ба таҳияи компилятор барои забони хеле содда назар мекунад. Ҳардуи ин манбаъҳои иттилоот барои шумо ҳангоми сохтани компилятори шахсии худ муфид хоҳанд буд.

Хонандагони азиз! Оё шумо LLVM-ро истифода мебаред?

LLVM аз нуқтаи назари Go

Манбаъ: will.com

Илова Эзоҳ