Programmering

Hvad er LLVM? Styrken bag Swift, Rust, Clang og mere

Nye sprog og forbedringer af eksisterende sprog svampe gennem det udviklende landskab. Mozillas Rust, Apples Swift, Jetbrains's Kotlin og mange andre sprog giver udviklere en ny række valgmuligheder for hastighed, sikkerhed, bekvemmelighed, bærbarhed og magt.

Hvorfor nu? En stor grund er nye værktøjer til at opbygge sprog - specifikt kompilatorer. Og chef blandt dem er LLVM, et open source-projekt, der oprindeligt blev udviklet af den hurtige sprogskaber Chris Lattner som et forskningsprojekt ved University of Illinois.

LLVM gør det lettere at ikke kun oprette nye sprog, men at forbedre udviklingen af ​​eksisterende sprog. Det giver værktøjer til automatisering af mange af de mest utaknemmelige dele af opgaven med sprogoprettelse: Oprettelse af en kompilator, overførsel af den udsendte kode til flere platforme og arkitekturer, generering af arkitekturspecifikke optimeringer såsom vektorisering og skrivning af kode til håndtering af almindelige sprogmetaforer som undtagelser. Dens liberale licens betyder, at den frit kan genbruges som en softwarekomponent eller implementeres som en tjeneste.

Listen over sprog, der bruger LLVM, har mange velkendte navne. Apples Swift-sprog bruger LLVM som sin kompilatorramme, og Rust bruger LLVM som en kernekomponent i sin værktøjskæde. Også mange compilere har en LLVM-udgave, såsom Clang, C / C ++ -compilatoren (dette er navnet, "C-lang"), i sig selv et projekt tæt knyttet til LLVM. Mono .NET-implementeringen har mulighed for at kompilere til native-kode ved hjælp af en LLVM-back-end. Og Kotlin, nominelt et JVM-sprog, udvikler en version af sproget kaldet Kotlin Native, der bruger LLVM til at kompilere til maskinindfødt kode.

LLVM defineret

I sin kerne er LLVM et bibliotek til programmatisk oprettelse af maskinindfødt kode. En udvikler bruger API'en til at generere instruktioner i et format kaldet en mellemrepræsentationeller IR. LLVM kan derefter kompilere IR til en enkeltstående binær eller udføre en JIT (just-in-time) kompilering på koden, der skal køres i sammenhæng med et andet program, såsom en tolk eller runtime for sproget.

LLVM's API'er er primitive til at udvikle mange almindelige strukturer og mønstre, der findes i programmeringssprog. For eksempel har næsten ethvert sprog begrebet en funktion og en global variabel, og mange har coroutines og C-udenlandske funktionsgrænseflader. LLVM har funktioner og globale variabler som standardelementer i sin IR og har metaforer til oprettelse af coroutines og grænseflade med C-biblioteker.

I stedet for at bruge tid og energi på at genopfinde de specifikke hjul, kan du bare bruge LLVMs implementeringer og fokusere på de dele af dit sprog, der har brug for opmærksomheden.

Læs mere om Go, Kotlin, Python og Rust

Gå:

  • Tryk på kraften i Googles Go-sprog
  • De bedste Go-sprog IDE'er og redaktører

Kotlin:

  • Hvad er Kotlin? Java-alternativet forklaret
  • Kotlin-rammer: En undersøgelse af JVM-udviklingsværktøjer

Python:

  • Hvad er Python? Alt hvad du behøver at vide
  • Tutorial: Sådan kommer du i gang med Python
  • 6 vigtige biblioteker til enhver Python-udvikler

Rust:

  • Hvad er rust? Vejen til sikker, hurtig og nem softwareudvikling
  • Lær hvordan du kommer i gang med Rust

LLVM: Designet til bærbarhed

For at forstå LLVM kan det hjælpe med at overveje en analogi med C-programmeringssproget: C beskrives undertiden som et bærbart monteringssprog på højt niveau, fordi det har konstruktioner, der kan kortlægges tæt på systemhardware, og det er blevet portet til næsten hver systemarkitektur. Men C er kun nyttigt som et bærbart samlingssprog op til et punkt; det var ikke designet til det særlige formål.

I modsætning hertil blev LLVMs IR designet fra starten til at være en bærbar enhed. En måde det opnår denne bærbarhed på er at tilbyde primitive uafhængige af enhver bestemt maskinarkitektur. F.eks. Er heltalstyper ikke begrænset til den maksimale bitbredde for den underliggende hardware (såsom 32 eller 64 bit). Du kan oprette primitive heltalstyper ved hjælp af så mange bits som nødvendigt, som et 128-bit heltal. Du behøver heller ikke bekymre dig om at lave output, så det matcher en specifik processor's instruktions sæt; LLVM tager sig af det også for dig.

LLVMs arkitekturneutrale design gør det lettere at understøtte hardware af enhver art, nutid og fremtid. For eksempel bidrog IBM for nylig med kode til at understøtte sit z / OS, Linux on Power (inklusive support til IBMs MASS-vektoriseringsbibliotek) og AIX-arkitekturer til LLVM's C-, C ++- og Fortran-projekter.

Hvis du vil se live eksempler på LLVM IR, skal du gå til ELLCC Project-webstedet og prøve den live demo, der konverterer C-kode til LLVM IR lige i browseren.

Hvordan programmeringssprog bruger LLVM

Den mest almindelige brugssag til LLVM er som en AOT-kompilator for et sprog. For eksempel kompilerer Clang-projektet i forvejen C og C ++ til oprindelige binære filer. Men LLVM gør også andre ting mulige.

Just-in-time kompilering med LLVM

I nogle situationer kræves der, at der genereres kode i farten ved kørsel, snarere end at kompilere på forhånd. Julia-sproget, for eksempel, JIT-kompilerer sin kode, fordi det skal køre hurtigt og interagere med brugeren via en REPL (read-eval-print loop) eller en interaktiv prompt.

Numba, en matematisk accelerationspakke til Python, JIT-kompilerer udvalgte Python-funktioner til maskinkode. Det kan også kompilere Numba-dekoreret kode på forhånd, men (som Julia) tilbyder Python hurtig udvikling ved at være et fortolket sprog. Brug af JIT-kompilering til at producere sådan kode supplerer Pythons interaktive arbejdsgang bedre end kompilering på forhånd.

Andre eksperimenterer med nye måder at bruge LLVM som en JIT, såsom at kompilere PostgreSQL-forespørgsler, hvilket giver op til en femdobling af ydeevnen.

Automatisk kodeoptimering med LLVM

LLVM kompilerer ikke bare IR til native maskinkode. Du kan også programmere den til at optimere koden med en høj grad af granularitet, hele vejen igennem sammenkædningsprocessen. Optimeringerne kan være ret aggressive, herunder ting som indlejring af funktioner, eliminering af død kode (inklusive ubrugte typedeklarationer og funktionsargumenter) og udrulning af sløjfer.

Igen er magten i ikke at skulle implementere alt dette selv. LLVM kan håndtere dem for dig, eller du kan dirigere det for at skifte dem efter behov. For eksempel, hvis du vil have mindre binære filer på bekostning af en eller anden ydeevne, kan du få din compiler-frontend til at fortælle LLVM at deaktivere loopudrulning.

Domænespecifikke sprog med LLVM

LLVM er blevet brugt til at producere compilers til mange sprog til generelle formål, men det er også nyttigt til at producere sprog, der er meget lodrette eller eksklusive for et problemdomæne. På nogle måder er det her, hvor LLVM skinner klarest, fordi det fjerner meget trængsel ved at skabe et sådant sprog og får det til at fungere godt.

Emscripten-projektet tager for eksempel LLVM IR-kode og konverterer den til JavaScript, hvilket i teorien tillader ethvert sprog med en LLVM-back-end til eksportkode, der kan køre i browseren. Den langsigtede plan er at have LLVM-baserede bagenden, der kan producere WebAss Assembly, men Emscripten er et godt eksempel på, hvor fleksibel LLVM kan være.

En anden måde, hvorpå LLVM kan bruges, er at tilføje domænespecifikke udvidelser til et eksisterende sprog. Nvidia brugte LLVM til at oprette Nvidia CUDA Compiler, som lader sprog tilføje native support til CUDA, der kompileres som en del af den oprindelige kode, du genererer (hurtigere), i stedet for at blive påberåbt gennem et bibliotek, der leveres med det (langsommere).

LLVMs succes med domænespecifikke sprog har ansporet nye projekter inden for LLVM til at løse de problemer, de skaber. Det største problem er, hvordan nogle DSL'er er svære at oversætte til LLVM IR uden meget hårdt arbejde i frontenden. En løsning på værkerne er Multi-Level Intermediate Representation eller MLIR-projektet.

MLIR giver praktiske måder at repræsentere komplekse datastrukturer og operationer, som derefter kan oversættes automatisk til LLVM IR. For eksempel kunne TensorFlow-maskinindlæringsrammen have mange af sine komplekse dataflytningsgrafoperationer effektivt kompileret til native-kode med MLIR.

Arbejde med LLVM på forskellige sprog

Den typiske måde at arbejde med LLVM på er via kode på et sprog, du er fortrolig med (og som selvfølgelig understøtter LLVMs biblioteker).

To almindelige sprogvalg er C og C ++. Mange LLVM-udviklere er standard for en af ​​disse to af flere gode grunde:

  • LLVM selv er skrevet i C ++.
  • LLVM's API'er fås i C- og C ++ -inkarnationer.
  • Meget sprogudvikling har tendens til at ske med C / C ++ som en base

Alligevel er disse to sprog ikke de eneste valg. Mange sprog kan kalde indbygget i C-biblioteker, så det er teoretisk muligt at udføre LLVM-udvikling med ethvert sådant sprog. Men det hjælper med at have et faktisk bibliotek på det sprog, der elegant indpakker LLVMs API'er. Heldigvis har mange sprog og sprogkørselstider sådanne biblioteker, herunder C # /. NET / Mono, Rust, Haskell, OCAML, Node.js, Go og Python.

En advarsel er, at nogle af sprogbindingerne til LLVM kan være mindre komplette end andre. Med Python er der for eksempel mange valg, men hver varierer i sin fuldstændighed og anvendelighed:

  • llvmlite, udviklet af teamet, der opretter Numba, er fremstået som den nuværende konkurrent til at arbejde med LLVM i Python. Den implementerer kun en delmængde af LLVMs funktionalitet, som dikteret af Numba-projektets behov. Men denne delmængde giver langt størstedelen af, hvad LLVM-brugere har brug for. (llvmlite er generelt det bedste valg til at arbejde med LLVM i Python.)
  • LLVM-projektet opretholder sit eget sæt bindinger til LLVMs C API, men de opretholdes i øjeblikket ikke.
  • llvmpy, den første populære Python-binding til LLVM, faldt ud af vedligeholdelse i 2015. Dårligt for ethvert softwareprojekt, men værre, når man arbejder med LLVM i betragtning af antallet af ændringer, der følger med i hver udgave af LLVM.
  • llvmcpy sigter mod at ajourføre Python-bindingerne til C-biblioteket, holde dem opdateret på en automatisk måde og gøre dem tilgængelige ved hjælp af Pythons native idiomer. llvmcpy er stadig i de tidlige stadier, men kan allerede udføre noget rudimentært arbejde med LLVM API'erne.

Hvis du er nysgerrig efter, hvordan du bruger LLVM-biblioteker til at opbygge et sprog, har LLVMs egne skabere en tutorial, der bruger enten C ++ eller OCAML, der hjælper dig med at skabe et simpelt sprog kaldet Kalejdoskop. Det er siden blevet overført til andre sprog:

  • Haskell:En direkte port til den originale tutorial.
  • Python: En sådan port følger selvstudiet nøje, mens den anden er en mere ambitiøs omskrivning med en interaktiv kommandolinje. Begge disse bruger llvmlite som bindinger til LLVM.
  • RustogHurtig: Det syntes uundgåeligt, at vi ville få havne i vejledningen til to af de sprog, som LLVM hjalp med at skabe.

Endelig er vejledningen også tilgængelig ihuman Sprog. Det er blevet oversat til kinesisk ved hjælp af den originale C ++ og Python.

Hvad LLVM ikke gør

Med alt hvad LLVM giver, er det nyttigt at også vide, hvad det ikke gør.

For eksempel analyserer LLVM ikke sprogets grammatik. Mange værktøjer gør allerede det job, som lex / yacc, flex / bison, Lark og ANTLR. Analyse er alligevel beregnet til at blive afkoblet fra kompilering, så det er ikke overraskende, at LLVM ikke prøver at løse noget af dette.

LLVM adresserer heller ikke direkte den større kultur af software omkring et givet sprog. Installation af kompilatorens binære filer, styring af pakker i en installation og opgradering af værktøjskæden - du skal gøre det alene.

Endelig og vigtigst af alt er der stadig fælles dele af sprog, som LLVM ikke giver primitiver til. Mange sprog har en eller anden måde affaldsindsamlet hukommelsesadministration, enten som den vigtigste måde at styre hukommelsen på eller som et supplement til strategier som RAII (som C ++ og Rust bruger). LLVM giver dig ikke en skraldopsamlingsmekanisme, men det giver værktøjer til at implementere skraldindsamling ved at lade kode markeres med metadata, der gør det lettere at skrive skraldespandere.

Intet af dette udelukker dog muligheden for, at LLVM i sidste ende kan tilføje indfødte mekanismer til implementering af affaldsindsamling. LLVM udvikler sig hurtigt med en større frigivelse hvert halve år. Og tempoet i udviklingen vil sandsynligvis kun stige takket være den måde, som mange aktuelle sprog har sat LLVM i centrum for deres udviklingsproces.

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