LLVM tina sudut pandang Go

Ngembangkeun kompiler mangrupikeun tugas anu sesah. Tapi, untungna, jeung ngembangkeun proyék kawas LLVM, leyuran pikeun masalah ieu greatly disederhanakeun, anu ngamungkinkeun malah hiji programmer tunggal nyieun basa anyar nu deukeut dina kinerja C. Gawe sareng LLVM téh nyusahkeun ku kanyataan yén ieu. Sistim ieu digambarkeun ku jumlah badag kode, dilengkepan saeutik dokuméntasi. Dina raraga nyobian ngabenerkeun shortcoming ieu, panulis bahan, tarjamahan nu urang medarkeun kiwari, bade demonstrate conto kode ditulis dina Go tur némbongkeun kumaha aranjeunna mimiti ditarjamahkeun kana. Hayu SSA, lajeng di LLVM IR ngagunakeun compiler tinyGO. Kodeu Go SSA sareng LLVM IR parantos rada diédit pikeun ngaleungitkeun hal-hal anu henteu relevan sareng katerangan anu dipasihkeun di dieu, supados katerangan langkung kaharti.

LLVM tina sudut pandang Go

conto kahiji

Fungsi kahiji anu kuring badé tingali di dieu nyaéta mékanisme saderhana pikeun nambihan nomer:

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

Pungsi ieu saderhana pisan, sareng, sigana, teu aya anu langkung saderhana. Éta ditarjamahkeun kana kode Go SSA ieu:

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

Kalayan pandangan ieu, petunjuk jinis data disimpen di sisi katuhu sareng tiasa dipaliré dina kalolobaan kasus.

conto leutik ieu geus ngidinan Anjeun pikeun ningali hakekat hiji aspék SSA. Nyaéta, nalika ngarobih kode kana bentuk SSA, unggal éksprési dirobih janten bagian anu paling dasar anu diwangun. Dina kasus urang, paréntah return a + b, kanyataanna, ngagambarkeun dua operasi: nambahkeun dua angka na balik hasilna.

Salaku tambahan, di dieu anjeun tiasa ningali blok dasar program; dina kode ieu ngan aya hiji blok - blok éntri. Urang bakal ngobrol langkung seueur ngeunaan blok di handap.

Kodeu Go SSA gampang dirobih kana LLVM IR:

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

Anu anjeun tiasa perhatikeun nyaéta sanaos struktur sintaksis anu béda dianggo di dieu, struktur fungsina dina dasarna henteu robih. Kode IR LLVM nyaéta saeutik kuat batan kode Go SSA, sarupa jeung C. Di dieu, dina deklarasi fungsi, mimiti aya pedaran tipe data eta mulih, tipe argumen dituduhkeun saméméh ngaran argumen. Salaku tambahan, pikeun nyederhanakeun parsing IR, nami éntitas global diawalan ku simbol @, sarta saméméh ngaran lokal aya simbol % (fungsi ogé dianggap éntitas global).

Hiji hal anu kedah diperhatoskeun ngeunaan kode ieu nyaéta kaputusan perwakilan jinis Go int, nu bisa digambarkeun salaku nilai 32-bit atawa 64-bit, gumantung kana compiler jeung udagan kompilasi nu, ditarima nalika LLVM ngahasilkeun kode IR. Ieu salah sahiji loba alesan yén LLVM kode IR teu, sakumaha loba jalma mikir, platform bebas. Kode sapertos kitu, diciptakeun pikeun hiji platform, teu tiasa ngan saukur dicandak sareng disusun pikeun platform anu sanés (kacuali anjeun cocog pikeun ngarengsekeun masalah ieu. kalayan ati-ati pisan).

titik metot séjén sia noting éta jenis i64 sanes integer ditandatanganan: éta nétral dina watesan ngagambarkeun tanda angka. Gumantung kana parentah, éta bisa ngagambarkeun duanana angka ditandatanganan sarta unsigned. Dina kasus ngagambarkeun operasi tambahan, ieu teu jadi masalah, jadi euweuh bédana dina gawé bareng nomer ditandatanganan atawa unsigned. Di dieu abdi hoyong dicatet yén dina basa C, overflowing variabel integer ditandatanganan ngabalukarkeun kabiasaan undefined, jadi frontend Clang nambahkeun bandéra kana operasi. nsw (euweuh bungkus ditandatanganan), nu ngabejaan LLVM yén éta bisa nganggap tambahan nu pernah overflows.

Ieu bisa jadi penting pikeun sababaraha optimizations. Contona, nambahkeun dua nilai i16 dina platform 32-bit (kalawan registers 32-bit) merlukeun, sanggeus tambahan, operasi ékspansi tanda guna tetep dina rentang. i16. Kusabab ieu, éta mindeng leuwih efisien nedunan operasi integer dumasar kana ukuran mesin register.

Naon anu lumangsung salajengna sareng kode IR ieu henteu dipikaresep ku urang ayeuna. Kodeu dioptimalkeun (tapi dina kasus conto saderhana sapertos urang, teu aya anu dioptimalkeun) teras dirobih janten kode mesin.

Conto kadua

Conto salajengna anu bakal urang tingali bakal langkung rumit. Nyaéta, urang ngobrol ngeunaan fungsi anu nyimpulkeun sapotong integer:

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

Kode ieu dirobih kana kode Go SSA di handap ieu:

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

Di dieu anjeun geus bisa ningali deui constructions has pikeun ngagambarkeun kode dina formulir SSA. Panginten fitur anu paling atra tina kode ieu nyaéta kanyataan yén henteu aya paréntah kontrol aliran terstruktur. Pikeun ngadalikeun aliran itungan, aya ngan luncat kondisional jeung saratna, jeung, lamun urang nganggap paréntah ieu salaku paréntah pikeun ngadalikeun aliran, paréntah mulang.

Nyatana, di dieu anjeun tiasa nengetan kanyataan yén programna henteu dibagi kana blok nganggo braces keriting (sapertos dina kulawarga basa C). Ieu dibagi ku labél, reminiscent tina basa assembly, sarta dibere dina bentuk blok dasar. Dina SSA, blok dasar didefinisikeun salaku runtuyan kode anu padeukeut dimimitian ku labél jeung ditungtungan ku parentah ngalengkepan blok dasar, kayaning - return и jump.

rinci metot séjén tina kode ieu digambarkeun ku instruksi phi. Parentahna rada teu biasa sareng peryogi sababaraha waktos kanggo ngartos. inget, éta S.S.A. nyaeta pondok pikeun Static Single Assignment. Ieu mangrupikeun perwakilan perantara kode anu dianggo ku kompiler, dimana unggal variabel ditugaskeun nilai ngan sakali. Ieu gede pikeun nganyatakeun fungsi basajan kawas fungsi urang myAddditémbongkeun di luhur, tapi teu cocog pikeun fungsi leuwih kompleks saperti fungsi dibahas dina bagian ieu sum. Khususna, variabel robih salami palaksanaan loop i и n.

SSA ngalangkungan larangan dina nangtukeun nilai variabel sakali nganggo anu disebut instruksi phi (ngaranna dicokot tina alfabét Yunani). Kanyataanna nyaéta supados perwakilan kode SSA dibangkitkeun pikeun basa sapertos C, anjeun kedah nganggo sababaraha trik. Hasil tina nelepon instruksi ieu mangrupa nilai ayeuna variabel (i atawa n), sareng daptar blok dasar dianggo salaku parameterna. Contona, pertimbangkeun parentah ieu:

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

Hartina kieu: lamun blok dasar saméméhna éta blok entry (input), teras t0 mangrupa konstanta 0, sarta lamun blok dasar saméméhna éta for.body, teras anjeun kedah nyandak nilai t6 ti blok ieu. Ieu sadayana sigana rada misterius, tapi mékanisme ieu anu ngajadikeun SSA jalan. Ti sudut pandang manusa, ieu sadayana ngajadikeun kodeu hésé ngarti, tapi kanyataan yén unggal nilai ditugaskeun ngan sakali ngajadikeun loba optimizations loba gampang.

Catet yén upami anjeun nyerat kompiler anjeun nyalira, anjeun biasana henteu kedah ngurus hal-hal sapertos kieu. Malah Clang henteu ngahasilkeun sadaya paréntah ieu phi, éta ngagunakeun mékanisme a alloca (eta nyarupaan gawé bareng variabel lokal biasa). Lajeng, nalika ngajalankeun hiji pass optimasi LLVM disebut mem2reg, parentah alloca dirobih kana bentuk SSA. TinyGo, kumaha oge, narima input ti Go SSA, nu, merenah, geus dirobah jadi formulir SSA.

Inovasi sejen tina fragmen kode perantara anu dipertimbangkeun nyaéta aksés kana unsur nyiksikan ku indéks digambarkeun dina bentuk operasi ngitung alamat sareng operasi dereferencing pointer anu dihasilkeun. Di dieu anjeun tiasa ningali tambihan langsung konstanta kana kode IR (contona - 1:int). Dina conto kalayan fungsi myAdd ieu teu acan dipaké. Ayeuna urang ngagaduhan fitur-fitur éta, hayu urang tingali naon kode ieu janten nalika dirobih kana bentuk 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
}

Di dieu, sakumaha sateuacanna, urang tiasa ningali struktur anu sami, anu kalebet struktur sintaksis anu sanés. Contona, dina nelepon phi nilai sareng labél diganti. Sanajan kitu, aya hiji hal di dieu anu patut nengetan husus.

Pikeun mimitian, di dieu anjeun tiasa ningali tanda tangan fungsi anu béda-béda. LLVM henteu ngadukung irisan, sareng salaku hasilna, salaku optimasi, kompiler TinyGo anu ngahasilkeun kode perantara ieu ngabagi déskripsi struktur data ieu kana sababaraha bagian. Éta tiasa ngawakilan tilu unsur nyiksikan (ptr, len и cap) salaku struktur (struct), tapi ngagambarkeun aranjeunna salaku tilu éntitas misah ngamungkinkeun pikeun sababaraha optimizations. Compiler séjén bisa ngagambarkeun keureutan ku cara nu sejen, gumantung kana konvénsi nelepon pungsi platform target urang.

fitur metot séjén tina kode ieu pamakéan instruksi getelementptr (mindeng disingget jadi GEP).

Instruksi ieu jalan kalawan pointers sarta dipaké pikeun ménta pointer ka unsur keureutan. Contona, urang bandingkeun jeung kode di handap ieu ditulis dina C:

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

Atanapi sareng anu sami sareng ieu:

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

Hal anu paling penting di dieu nyaéta paréntahna getelementptr henteu ngalakukeun operasi dereferencing. Éta ngan ukur ngitung pointer énggal dumasar kana anu tos aya. Ieu bisa dicokot salaku parentah mul и add dina tingkat hardware. Anjeun tiasa maca langkung seueur ngeunaan petunjuk GEP di dieu.

fitur metot séjén tina kode panengah ieu pamakéan instruksi icmp. Ieu instruksi tujuan umum dipaké pikeun nerapkeun ngabandingkeun integer. Hasil tina instruksi ieu salawasna nilai tipe i1 - nilai logis. Dina hal ieu, babandingan dilakukeun ngagunakeun kecap konci slt (ditandatanganan kirang ti), saprak urang ngabandingkeun dua angka saméméhna digambarkeun ku jenis int. Lamun urang ngabandingkeun dua integer unsigned, lajeng urang bakal ngagunakeun icmp, sareng kecap konci anu dianggo dina ngabandingkeun bakal ult. Pikeun ngabandingkeun angka floating point, instruksi séjén dipaké, fcmp, nu gawéna dina cara nu sarupa.

hasil

Kuring yakin yén dina bahan ieu Kuring geus katutupan fitur pangpentingna LLVM IR. Tangtosna, seueur deui di dieu. Khususna, perwakilan perantara kode tiasa ngandung seueur anotasi anu ngamungkinkeun pas optimasi pikeun tumut kana fitur-fitur tangtu kode anu dipikanyaho ku kompiler anu henteu tiasa dinyatakeun dina IR. Contona, ieu bandéra inbounds parentah GEP, atawa bandéra nsw и nuw, nu bisa ditambahkeun kana parentah add. Sami lumaku pikeun keyword private, nunjukkeun ka optimizer yén fungsi eta nandaan moal referenced ti luar Unit kompilasi ayeuna. Ieu ngamungkinkeun pikeun seueur optimasi interprocedural anu pikaresepeun sapertos ngaleungitkeun argumen anu henteu dianggo.

Anjeun tiasa maca langkung seueur ngeunaan LLVM di dokuméntasi, anu sering anjeun rujuk nalika ngembangkeun kompiler dumasar LLVM anjeun nyalira. Ieuh kapamimpinan, nu kasampak dina ngamekarkeun compiler pikeun basa basajan pisan. Kadua sumber inpormasi ieu bakal mangpaat pikeun anjeun nalika nyiptakeun kompiler anjeun nyalira.

Pamiarsa Hadirin! Dupi anjeun nganggo LLVM?

LLVM tina sudut pandang Go

sumber: www.habr.com

Tambahkeun komentar