Programmering

Hva er LLVM? Kraften bak Swift, Rust, Clang og mer

Nye språk, og forbedringer av eksisterende, sopper i hele det utviklende landskapet. Mozillas Rust, Apples Swift, Jetbrains's Kotlin og mange andre språk gir utviklere et nytt utvalg av valg for hastighet, sikkerhet, bekvemmelighet, bærbarhet og kraft.

Hvorfor nå? En stor grunn er nye verktøy for å bygge språk - spesielt kompilatorer. Og sjefen blant dem er LLVM, et open source-prosjekt som opprinnelig ble utviklet av Swift-språkskaperen Chris Lattner som et forskningsprosjekt ved University of Illinois.

LLVM gjør det lettere å ikke bare opprette nye språk, men å forbedre utviklingen av eksisterende. Det gir verktøy for å automatisere mange av de mest takknemlige delene av språklaget: lage en kompilator, portere den utgitte koden til flere plattformer og arkitekturer, generere arkitekturspesifikke optimaliseringer som vektorisering og skrive kode for å håndtere vanlige språkmetaforer unntak. Dens liberale lisensiering betyr at den kan brukes fritt på nytt som en programvarekomponent eller distribueres som en tjeneste.

Liste over språk som bruker LLVM har mange kjente navn. Apples Swift-språk bruker LLVM som kompilatorrammeverk, og Rust bruker LLVM som en kjernekomponent i verktøykjeden. Også mange kompilatorer har en LLVM-utgave, som Clang, C / C ++ -kompilatoren (dette navnet, "C-lang"), som i seg selv er et prosjekt nært knyttet til LLVM. Mono, .NET-implementeringen, har muligheten til å kompilere til innfødt kode ved hjelp av en LLVM-bakside. Og Kotlin, nominelt et JVM-språk, utvikler en versjon av språket som heter Kotlin Native som bruker LLVM til å kompilere til maskininnfødt kode.

LLVM definert

I sitt hjerte er LLVM et bibliotek for programmatisk oppretting av maskininnfødt kode. En utvikler bruker API for å generere instruksjoner i et format som kalles en mellomrepresentasjon, eller IR. LLVM kan deretter kompilere IR til en frittstående binær eller utføre en JIT (just-in-time) kompilering på koden for å kjøre i sammenheng med et annet program, for eksempel en tolk eller kjøretid for språket.

LLVMs API-er gir primitiver for å utvikle mange vanlige strukturer og mønstre som finnes i programmeringsspråk. For eksempel har nesten alle språk konseptet med en funksjon og en global variabel, og mange har coroutines og C utenlandske funksjonsgrensesnitt. LLVM har funksjoner og globale variabler som standardelementer i sin IR, og har metaforer for å lage coroutines og grensesnitt med C-biblioteker.

I stedet for å bruke tid og energi på å finne ut de aktuelle hjulene, kan du bare bruke LLVMs implementeringer og fokusere på de delene av språket ditt som trenger oppmerksomhet.

Les mer om Go, Kotlin, Python og Rust

Gå:

  • Trykk på kraften til Googles Go-språk
  • De beste Go-språket IDEer og redaktører

Kotlin:

  • Hva er Kotlin? Java-alternativet forklart
  • Kotlin rammer: En undersøkelse av JVM utviklingsverktøy

Python:

  • Hva er Python? Alt du trenger å vite
  • Opplæring: Hvordan komme i gang med Python
  • 6 viktige biblioteker for hver Python-utvikler

Rust:

  • Hva er Rust? Måten å gjøre sikker, rask og enkel programvareutvikling på
  • Lær hvordan du kommer i gang med Rust

LLVM: Designet for bærbarhet

For å forstå LLVM kan det hjelpe å vurdere en analogi med C-programmeringsspråket: C blir noen ganger beskrevet som et bærbart monteringsspråk på høyt nivå, fordi det har konstruksjoner som kan kartlegges nært systemhardware, og det har blitt portet til nesten hver systemarkitektur. Men C er nyttig som et bærbart monteringsspråk bare opp til et punkt; den var ikke designet for det spesielle formålet.

Derimot ble LLVMs IR designet fra begynnelsen til å være en bærbar forsamling. En måte det oppnår denne bærbarheten på er å tilby primitiver uavhengig av en bestemt maskinarkitektur. Heltallstyper er for eksempel ikke begrenset til den maksimale bitbredden til den underliggende maskinvaren (for eksempel 32 eller 64 bits). Du kan lage primitive heltallstyper ved å bruke så mange biter som nødvendig, som et 128-biters heltall. Du trenger heller ikke å bekymre deg for å lage utdata for å matche en spesifikk prosessorens instruksjonssett; LLVM tar seg av det også for deg.

LLVMs arkitekturneutrale design gjør det lettere å støtte maskinvare av alle slag, nåtid og fremtid. For eksempel bidro IBM nylig med kode for å støtte z / OS, Linux on Power (inkludert støtte for IBMs MASS-vektoriseringsbibliotek) og AIX-arkitekturer for LLVMs C-, C ++- og Fortran-prosjekter.

Hvis du vil se liveeksempler på LLVM IR, kan du gå til ELLCC Project-nettstedet og prøve live demo som konverterer C-kode til LLVM IR rett i nettleseren.

Hvordan programmeringsspråk bruker LLVM

Den vanligste brukssaken for LLVM er som en AOT-kompilator for et språk. For eksempel kompilerer Clang-prosjektet på forhånd C og C ++ til innfødte binære filer. Men LLVM gjør også andre ting mulig.

Just-in-time kompilering med LLVM

Noen situasjoner krever at det genereres kode mens du kjører, i stedet for å kompilere på forhånd. Julia-språket, for eksempel, JIT-kompilerer koden sin, fordi den trenger å løpe raskt og samhandle med brukeren via en REPL (read-eval-print loop) eller interaktiv ledetekst.

Numba, en matematisk akselerasjonspakke for Python, kompilerer JIT utvalgte Python-funksjoner til maskinkode. Det kan også kompilere Numba-dekorert kode på forhånd, men (som Julia) tilbyr Python rask utvikling ved å være et tolket språk. Å bruke JIT-kompilering for å produsere slik kode utfyller Pythons interaktive arbeidsflyt bedre enn kompilering på forhånd.

Andre eksperimenterer med nye måter å bruke LLVM som en JIT, for eksempel å kompilere PostgreSQL-spørsmål, noe som gir opptil en femdobling av ytelsen.

Automatisk kodeoptimalisering med LLVM

LLVM kompilerer ikke bare IR til naturlig maskinkode. Du kan også programmere den for å optimalisere koden med en høy grad av granularitet, hele veien gjennom koblingsprosessen. Optimaliseringene kan være ganske aggressive, inkludert ting som å legge inn funksjoner, eliminere død kode (inkludert ubrukte typedeklarasjoner og funksjonsargumenter) og rulle løkker.

Igjen er kraften i å slippe å implementere alt dette selv. LLVM kan håndtere dem for deg, eller du kan dirigere det for å slå dem av etter behov. For eksempel, hvis du vil ha mindre binærfiler på bekostning av en eller annen ytelse, kan du få kompilatorfronten din til å fortelle LLVM å deaktivere sløyfing.

Domenespesifikke språk med LLVM

LLVM har blitt brukt til å produsere kompilatorer for mange generelle språk, men det er også nyttig for å produsere språk som er svært vertikale eller eksklusive for et problemdomene. På noen måter er det her LLVM skinner klarest, fordi det fjerner mye av sliter i å lage et slikt språk og får det til å fungere bra.

Emscripten-prosjektet tar for eksempel LLVM IR-kode og konverterer den til JavaScript, i teorien slik at ethvert språk med en LLVM-bakside kan eksportere kode som kan kjøres i nettleseren. Den langsiktige planen er å ha LLVM-baserte bakenden som kan produsere WebAssembly, men Emscripten er et godt eksempel på hvor fleksibel LLVM kan være.

En annen måte LLVM kan brukes på er å legge til domenespesifikke utvidelser til et eksisterende språk. Nvidia brukte LLVM til å lage Nvidia CUDA Compiler, som lar språk legge til innfødt støtte for CUDA som kompileres som en del av den opprinnelige koden du genererer (raskere), i stedet for å bli påkalt gjennom et bibliotek som leveres med det (tregere).

LLVMs suksess med domenespesifikke språk har ansporet nye prosjekter innen LLVM for å løse problemene de skaper. Det største problemet er hvordan noen DSL er vanskelig å oversette til LLVM IR uten mye hardt arbeid på fronten. En løsning i arbeidene er Multi-Level Intermediate Representation, eller MLIR-prosjektet.

MLIR gir praktiske måter å representere komplekse datastrukturer og operasjoner på, som deretter kan oversettes automatisk til LLVM IR. For eksempel kan TensorFlow maskinlæringsrammeverk ha mange av sine komplekse dataflyt-grafoperasjoner effektivt samlet til opprinnelig kode med MLIR.

Arbeider med LLVM på forskjellige språk

Den typiske måten å jobbe med LLVM på er via kode på et språk du er komfortabel med (og som selvfølgelig har støtte for LLVMs biblioteker).

To vanlige språkvalg er C og C ++. Mange LLVM-utviklere har som standard en av disse to av flere gode grunner:

  • LLVM selv er skrevet i C ++.
  • LLVMs API-er er tilgjengelige i C- og C ++ -inkarnasjoner.
  • Mye språkutvikling har en tendens til å skje med C / C ++ som en base

De to språkene er likevel ikke de eneste valgene. Mange språk kan kalle inn i C-biblioteker, så det er teoretisk mulig å utføre LLVM-utvikling med et slikt språk. Men det hjelper å ha et faktisk bibliotek på språket som elegant innpakker LLVMs API-er. Heldigvis har mange språk og språketid slike biblioteker, inkludert C # /. NET / Mono, Rust, Haskell, OCAML, Node.js, Go og Python.

En advarsel er at noen av språkbindingene til LLVM kan være mindre komplette enn andre. Med Python er det for eksempel mange valg, men hver varierer i fullstendighet og nytte:

  • llvmlite, utviklet av teamet som oppretter Numba, har dukket opp som den nåværende konkurrenten for å jobbe med LLVM i Python. Den implementerer bare en delmengde av LLVMs funksjonalitet, som diktert av behovene til Numba-prosjektet. Men den delmengden gir det store flertallet av det LLVM-brukere trenger. (llvmlite er vanligvis det beste valget for å jobbe med LLVM i Python.)
  • LLVM-prosjektet opprettholder sitt eget bindesett til LLVMs C API, men de vedlikeholdes foreløpig ikke.
  • llvmpy, den første populære Python-bindingen for LLVM, falt ut av vedlikehold i 2015. Dårlig for ethvert programvareprosjekt, men verre når du arbeider med LLVM, gitt antall endringer som følger med i hver utgave av LLVM.
  • llvmcpy tar sikte på å oppdatere Python-bindingene for C-biblioteket, holde dem oppdatert på en automatisert måte og gjøre dem tilgjengelige ved hjelp av Pythons opprinnelige idiomer. llvmcpy er fortsatt i de tidlige stadiene, men kan allerede gjøre litt rudimentært arbeid med LLVM API-ene.

Hvis du er nysgjerrig på hvordan du bruker LLVM-biblioteker til å bygge et språk, har LLVMs egne skapere en veiledning, som bruker enten C ++ eller OCAML, som veileder deg gjennom å lage et enkelt språk kalt Kalejdoskop. Siden har den blitt portet til andre språk:

  • Haskell:En direkte port av den originale opplæringen.
  • Python: En slik port følger opplæringen nøye, mens den andre er en mer ambisiøs omskriving med en interaktiv kommandolinje. Begge bruker llvmlite som bindinger til LLVM.
  • RustogFort: Det virket uunngåelig at vi ville få porter av opplæringen til to av språkene som LLVM bidro til å få til.

Endelig er opplæringen også tilgjengelig imenneskelig språk. Den er oversatt til kinesisk ved hjelp av originalen C ++ og Python.

Hva LLVM ikke gjør

Med alt det LLVM gir, er det nyttig å også vite hva den ikke gjør.

For eksempel analyserer LLVM ikke språkets grammatikk. Mange verktøy gjør allerede den jobben, som lex / yacc, flex / bison, Lark og ANTLR. Parsing er ment å være frikoblet fra kompilering uansett, så det er ikke overraskende at LLVM ikke prøver å ta opp noe av dette.

LLVM adresserer heller ikke direkte den større programvarekulturen rundt et gitt språk. Installere kompilatorens binære filer, administrere pakker i en installasjon og oppgradere verktøykjeden - du må gjøre det alene.

Til slutt, og viktigst, er det fortsatt vanlige deler av språk som LLVM ikke gir primitiver for. Mange språk har en eller annen form for søppelinnsamlet minneadministrasjon, enten som den viktigste måten å administrere minne på eller som et supplement til strategier som RAII (som C ++ og Rust bruker). LLVM gir deg ikke en søppeloppsamlingsmekanisme, men den gir verktøy for å implementere søppeloppsamling ved å la kode merkes med metadata som gjør det lettere å skrive søppeloppsamlere.

Ingenting av dette utelukker imidlertid muligheten for at LLVM til slutt kan legge til innfødte mekanismer for å implementere søppeloppsamling. LLVM utvikler seg raskt, med en stor utgivelse hvert sjette halvår. Og tempoet i utviklingen vil sannsynligvis bare øke takket være måten mange nåværende språk har satt LLVM i sentrum av utviklingsprosessen.

$config[zx-auto] not found$config[zx-overlay] not found