Što je LLVM? Snaga iza Swifta, Rust, Clang i još mnogo toga

Novi jezici i poboljšanja postojećih razvijaju se kroz razvojni krajolik. Mozilla's Rust, Apple Swift, Jetbrains's Kotlin i mnogi drugi jezici pružaju programerima novi niz izbora brzine, sigurnosti, praktičnosti, prenosivosti i snage.

Zašto sada? Jedan od velikih razloga su novi alati za izgradnju jezika - posebno kompajleri. A glavni među njima je LLVM, projekt otvorenog koda koji je izvorno razvio kreator jezika Swift Chris Lattner kao istraživački projekt na Sveučilištu Illinois.

LLVM olakšava ne samo stvaranje novih jezika, već i poboljšanje razvoja postojećih. Pruža alate za automatizaciju mnogih najnezahvalnijih dijelova zadatka stvaranja jezika: stvaranje kompajlera, prijenos izlaznog koda na više platformi i arhitektura, generiranje specifičnih za arhitekturu optimizacija poput vektorizacije i pisanje koda za obradu metafora uobičajenog jezika poput iznimke. Njegovo liberalno licenciranje znači da se može slobodno ponovno koristiti kao softverska komponenta ili postaviti kao usluga.

Spisak jezika koji koriste LLVM ima mnoga poznata imena. Appleov jezik Swift koristi LLVM kao okvir za kompajliranje, a Rust koristi LLVM kao temeljnu komponentu svog lanca alata. Također, mnogi kompajleri imaju LLVM izdanje, poput Clang-a, C / C ++-ovog kompajlera (ovo je ime, "C-lang"), koji je i sam projekt usko povezan s LLVM-om. Mono, implementacija .NET-a, ima mogućnost kompajliranja u izvorni kôd pomoću pozadine LLVM-a. I Kotlin, nominalno JVM jezik, razvija verziju jezika nazvanu Kotlin Native koja koristi LLVM za kompajliranje u izvorni kod stroja.

Definiran LLVM

U svojoj srži, LLVM je knjižnica za programsko stvaranje strojnog koda. Razvojni programer koristi API za generiranje uputa u formatu koji se naziva posredni prikaz ili IR. LLVM tada može kompajlirati IR u samostalni binarni program ili izvesti JIT (just-in-time) kompilaciju na kodu koja će se izvoditi u kontekstu drugog programa, kao što je tumač ili runtime za jezik.

LLVM-ovi API-ji pružaju primitive za razvijanje mnogih uobičajenih struktura i obrazaca koji se nalaze u programskim jezicima. Na primjer, gotovo svaki jezik ima koncept funkcije i globalne varijable, a mnogi imaju korotaine i C sučelja sa stranim funkcijama. LLVM ima funkcije i globalne varijable kao standardne elemente u svom IR-u, a ima i metafore za stvaranje koprograma i povezivanje s C knjižnicama.

Umjesto da trošite vrijeme i energiju na pronalaženje tih određenih kotača, možete jednostavno koristiti implementacije LLVM-a i usredotočiti se na dijelove vašeg jezika kojima je potrebna pažnja.

Pročitajte više o Gou, Kotlinu, Pythonu i Rustu 

Ići:

  • Dodirnite snagu Googleovog jezika Go
  • Najbolji IDE-ovi i urednici na Go jeziku

Kotlin:

  • Što je Kotlin? Objašnjena je alternativa Java
  • Kotlinovi okviri: Istraživanje razvojnih alata JVM-a

Piton:

  • Što je Python? Sve što trebate znati
  • Vodič: Kako započeti s Pythonom
  • 6 osnovnih knjižnica za svakog programera Pythona

Rđa:

  • Što je Rust? Način za siguran, brz i lak razvoj softvera
  • Naučite kako započeti s Rustom 

LLVM: Dizajniran za prijenosnost

Da bismo razumjeli LLVM, moglo bi biti korisno razmotriti analogiju s programskim jezikom C: C se ponekad opisuje kao prijenosni montažni jezik visoke razine, jer ima konstrukcije koje se mogu usko preslikati na hardver sustava, a prebačen je na gotovo svaka arhitektura sustava. Ali C je koristan kao prijenosni montažni jezik samo do određene točke; nije dizajniran za tu određenu svrhu.

Suprotno tome, LLVM-ov IR je od početka dizajniran za prijenosni sklop. Jedan od načina na koji postiže ovu prenosivost jest nuđenje primitiva neovisnih o bilo kojoj određenoj arhitekturi stroja. Na primjer, vrste cijelih brojeva nisu ograničene na maksimalnu širinu bita osnovnog hardvera (kao što su 32 ili 64 bita). Možete stvoriti primitivne cijele brojeve koristeći onoliko bitova koliko je potrebno, poput 128-bitnog cijelog broja. Također se ne morate brinuti oko izrade rezultata kako bi odgovarao određenom skupu uputa procesora; LLVM se brine za to i za vas.

Arhitektonski neutralan dizajn LLVM-a olakšava podršku hardveru svih vrsta, sadašnjem i budućem. Na primjer, IBM je nedavno pridonio kodu za podršku svojih z / OS, Linux on Power (uključujući podršku za IBM-ovu biblioteku vektorizacije MASS) i AIX arhitekture za LLVM-ove projekte C, C ++ i Fortran. 

Ako želite uživo vidjeti primjere LLVM IR-a, idite na web mjesto projekta ELLCC i isprobajte demonstraciju uživo koja pretvara C kôd u LLVM IR izravno u pregledniku.

Kako programski jezici koriste LLVM

Najčešći slučaj upotrebe LLVM-a je kao prerađivač vremena (AOT) za jezik. Na primjer, Clang-ov projekt ispred vremena kompajlira C i C ++ u izvorne binarne datoteke. Ali LLVM omogućuje i druge stvari.

Pravovremeno sastavljanje s LLVM-om

Neke situacije zahtijevaju da se kôd generira u letu tijekom izvođenja, a ne da se sastavlja prije vremena. Na primjer, jezik Julia JIT kompajlira svoj kôd, jer mora brzo raditi i komunicirati s korisnikom putem REPL-a (petlja čitanja-ispisa-ispisa) ili interaktivnog upita. 

Numba, paket za matematičko ubrzanje za Python, JIT kompilira odabrane Python funkcije u strojni kod. Također može sastaviti Numba ukrašeni kôd prije vremena, ali (poput Julije) Python nudi brz razvoj interpretiranim jezikom. Korištenje JIT kompilacije za izradu takvog koda dopunjuje Pythonov interaktivni tijek rada bolje od kompilacije prije vremena.

Drugi eksperimentiraju s novim načinima korištenja LLVM-a kao JIT-a, poput sastavljanja PostgreSQL upita, što dovodi do petostrukog povećanja performansi.

Automatska optimizacija koda s LLVM-om

LLVM ne kompajlira IR samo s matičnim strojnim kodom. Možete ga i programski usmjeriti tako da optimizira kôd s visokim stupnjem granulacije, sve do postupka povezivanja. Optimizacije mogu biti prilično agresivne, uključujući stvari poput ugradnje funkcija, uklanjanja mrtvog koda (uključujući neiskorištene deklaracije tipa i argumente funkcija) i odmotavanje petlji.

Opet, snaga je u tome što sve ovo ne morate sami provoditi. LLVM ih može riješiti umjesto vas ili ih možete usmjeriti da ih po potrebi isključi. Na primjer, ako želite manje binarne datoteke po cijenu neke izvedbe, prednjem dijelu kompajlera možete reći LLVM da onemogući odmotavanje petlje.

Jezici specifični za domenu s LLVM-om

LLVM je korišten za izradu kompajlera za mnoge jezike opće namjene, ali također je koristan za proizvodnju jezika koji su vrlo vertikalni ili ekskluzivni za problematičnu domenu. Na neki način, tu LLVM najsjajnije svijetli, jer uklanja veliku muku u stvaranju takvog jezika i čini ga uspješnim.

Projekt Emscripten, na primjer, uzima LLVM IR kôd i pretvara ga u JavaScript, u ​​teoriji dopuštajući izvozu koda koji može pokrenuti bilo koji jezik s LLVM pozadinom. Dugoročni je plan imati pozadine temeljene na LLVM-u koje mogu proizvesti WebAssembly, ali Emscripten je dobar primjer koliko LLVM može biti fleksibilan.

Drugi način na koji se LLVM može koristiti je dodavanje proširenja specifičnih za domenu postojećem jeziku. Nvidia je koristila LLVM za stvaranje Nvidia CUDA Compilera, koji omogućava jezicima da dodaju matičnu podršku za CUDA koja se kompajlira kao dio izvornog koda koji generirate (brže), umjesto da se poziva putem biblioteke isporučene s njim (sporije).

Uspjeh LLVM-a s jezicima specifičnim za domenu potaknuo je nove projekte u LLVM-u za rješavanje problema koje oni stvaraju. Najveće je pitanje kako je neke DSL-ove teško pretvoriti u LLVM IR bez puno napornog rada na prednjem kraju. Jedno od rješenja u radovima je višerazinsko srednje predstavljanje ili projekt MLIR.

MLIR pruža prikladne načine za predstavljanje složenih struktura podataka i operacija, koji se zatim mogu automatski prevesti u LLVM IR. Na primjer, okvir za strojno učenje TensorFlow mogao bi imati mnogo svojih složenih operacija grafikona toka podataka učinkovito kompilirane u izvorni kôd s MLIR-om.

Rad s LLVM-om na raznim jezicima

Tipičan način rada s LLVM-om je putem koda na jeziku koji vam odgovara (i koji naravno ima podršku za LLVM-ove knjižnice).

Dva uobičajena izbora jezika su C i C ++. Mnogi programeri LLVM-a zadaju jedan od ta dva iz nekoliko dobrih razloga: 

  • Sam LLVM napisan je na jeziku C ++.
  • LLVM-ovi API-ji dostupni su u C i C ++ inkarnacijama.
  • Mnogo se jezičnog razvoja obično događa sa C / C ++ kao bazom

Ipak, ta dva jezika nisu jedini izbor. Mnogi jezici mogu izvorno pozivati ​​C knjižnice, pa je teoretski moguće izvesti LLVM razvoj s bilo kojim takvim jezikom. Ali pomaže imati stvarnu biblioteku na jeziku koja elegantno omata LLVM-ove API-je. Srećom, mnogi jezici i runtimes jezika imaju takve knjižnice, uključujući C # /. NET / Mono, Rust, Haskell, OCAML, Node.js, Go i Python.

Jedno se upozorenje odnosi na to da su neke jezične vezivanja za LLVM možda manje cjelovite od drugih. Na primjer, s Pythonom postoji mnogo izbora, ali svaki se razlikuje u potpunosti i korisnosti:

  • llvmlite, koji je razvio tim koji stvara Numbu, pojavio se kao trenutni kandidat za rad s LLVM-om u Pythonu. Primjenjuje samo podskup funkcionalnosti LLVM-a, kako to diktiraju potrebe projekta Numba. Ali taj podskup pruža veliku većinu onoga što korisnicima LLVM-a treba. (llvmlite je općenito najbolji izbor za rad s LLVM-om u Pythonu.)
  • LLVM projekt održava vlastiti set veza na LLVM-ov C API, ali oni se trenutno ne održavaju.
  • llvmpy, prvo popularno vezivanje Pythona za LLVM, ispao je iz održavanja 2015. Loš za bilo koji softverski projekt, ali još gori pri radu s LLVM-om, s obzirom na broj promjena koje se pojavljuju u svakom izdanju LLVM-a.
  • llvmcpy ima za cilj ažurirati Pythonove vezove za C knjižnicu, ažurirati ih na automatiziran način i učiniti ih dostupnima koristeći Pythonove izvorne idiome. llvmcpy je još uvijek u ranoj fazi, ali već može obaviti neki osnovni posao s LLVM API-ima.

Ako ste znatiželjni o tome kako koristiti LLVM knjižnice za izgradnju jezika, vlastiti kreatori LLVM-a imaju vodič koji koristi C ++ ili OCAML koji vas vodi kroz stvaranje jednostavnog jezika nazvanog Kaleidoscope. Od tada je prebačen na druge jezike:

  • Haskell:  Izravni priključak izvornog vodiča.
  • Python: Jedna takva luka pomno prati tutorial, dok je druga ambicioznije prepisivanje s interaktivnim naredbenim retkom. Oboje koriste llvmlite kao veze za LLVM.
  • Rust  and  Swift: Činilo se neizbježnim da ćemo dobiti priključke za dva jezika na kojima je LLVM pomogao.

Napokon, vodič je dostupan i na  ljudskim jezicima. Preveden je na kineski jezik, koristeći izvorni C ++ i Python.

Ono što LLVM ne radi

Uz sve što LLVM pruža, korisno je znati i što ne čini.

Na primjer, LLVM ne raščlanjuje gramatiku jezika. Mnogi alati već rade taj posao, poput lex / yacc, flex / bison, Lark i ANTLR. Raščlanjivanje bi ionako trebalo odvojiti od kompilacije, pa ne čudi što LLVM ne pokušava riješiti ništa od ovoga.

LLVM se također ne odnosi izravno na širu kulturu softvera oko određenog jezika. Instaliranje binarnih datoteka kompajlera, upravljanje paketima u instalaciji i nadogradnja lanca alata - to morate učiniti sami.

Konačno, i najvažnije, još uvijek postoje uobičajeni dijelovi jezika za koje LLVM ne pruža primitive. Mnogi jezici imaju neki način upravljanja memorijom prikupljenim smećem, bilo kao glavni način upravljanja memorijom ili kao dodatak strategijama poput RAII (koje koriste C ++ i Rust). LLVM vam ne daje mehanizam za sakupljanje smeća, ali pruža alate za implementaciju sakupljanja smeća dopuštajući označavanje koda metapodacima što olakšava pisanje sakupljača smeća.

Međutim, ništa od toga ne isključuje mogućnost da bi LLVM na kraju mogao dodati nativne mehanizme za provedbu odvoza smeća. LLVM se brzo razvija, s velikim izdanjem svakih šest mjeseci. A tempo razvoja vjerojatno će se pojačati samo zahvaljujući načinu na koji su mnogi današnji jezici postavili LLVM u srce svog razvojnog procesa.