최근 수정 시각 : 2024-11-27 13:11:09

LLVM

LLVM/Clang에서 넘어옴
LLVM
파일:llvm-cropped.png
<colbgcolor=#ffffff,#1f2023> 종류 컴파일러 툴체인
초기 개발자 크리스 래트너, 비크람 애드비
개발 Apple → LLVM Group
첫 릴리즈 2003년 8월 24일
최신 버전 v19.1.4 (2024년 11월 19일)
개발 언어 C++
라이선스 NCSA Open Source License
아파치 라이선스 2.0
파일:홈페이지 아이콘.svg | 파일:GitHub 아이콘.svg파일:GitHub 아이콘 화이트.svg
1. 개요2. 구조3. LLVM 프로젝트
3.1. LLVM Core3.2. Clang
3.2.1. Swift-Clang3.2.2. Flang3.2.3. Kotlin/Native3.2.4. 그 외
3.3. LLDB3.4. lld

[clearfix]

1. 개요

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. Despite its name, LLVM has little to do with traditional virtual machines, though it does provide helpful libraries that can be used to build them. The name "LLVM" itself is not an acronym; it is the full name of the project.
LLVM 프로젝트는 모듈식의 재사용 가능한 컴파일러와 툴체인의 집합입니다. 이 이름에도 불구하고 LLVM은 기존의 가상머신과는 그것을 만드는데 도움이 될 수 있다는 점을 빼고는 거의 관련이 없습니다. "LLVM"이라는 이름은 약자가 아니며 그것이 프로젝트의 풀 네임입니다.

오픈 소스 컴파일러 프로젝트 또는 툴체인[1]의 이름.

미국 일리노이 대학교크리스 래트너(Chris Lattner)가 2000년에 쓴 석사 논문에서 출발한 프로젝트이며, 이후 2005년에 애플이 크리스 래트너를 스카웃한 뒤 LLVM 프로젝트를 적극적으로 지원하고 있다. 크리스 래트너는 2010년에 iOS프로그래밍 언어 Swift를, 2022년에는 프로그래밍 언어 Mojo를 만들기도 했다.

원래 LLVM은 저레벨 가상머신(Low-Level Virtual Machine)의 약자였다.[2] 하지만 프로젝트가 확장되며 이 용어는 더 이상 사용하지 않고 LLVM 자체가 프로젝트의 정식 명칭이 된다.

2. 구조

파일:llvm_infrastructure.png

컴파일러는 프론트엔드-미들엔드-백엔드의 단계로 구성되어 있다. 보통 이 세 단계는 하나의 프로그램으로 일괄 처리되는데, 이럴 경우 '언어의 종류 x 아키텍처의 종류'만큼 복수의 컴파일러가 필요하게 된다. 다양한 언어와 다양한 아키텍처에 대응할 수 있는 이식성이 중요한 요즘 이러한 컴파일러 구조는 재사용성을 떨어뜨린다는 문제가 있다. 바로 이것을 해결할 수 있는 컴파일러 구조가 LLVM이다.

위의 사진에서 보이듯, LLVM은 아키텍처별로 분리된 모듈식 미들엔드-백엔드를 중점으로 하고 있다. 프론트엔드가 여러가지 프로그래밍 언어들을 중간 표현 코드로 번역하고, LLVM은 그 중간 표현 코드를 각각의 아키텍처에 맞게 최적화하여 실행이 가능한 형태로 바꾸는 방식이다. 처음에는 GCC를 프론트엔드로 하고 LLVM은 미들엔드-백엔드로 사용하는 LLVM-GCC 프로젝트(DragonEgg)가 있었으나, LLVM의 자체 프론트엔드인 Clang이 등장한 이후 컴파일의 전 과정을 LLVM 툴체인으로 진행할 수 있게 되었다.

여기서 미들엔드와 백엔드는 LLVM Core라는 라이브러리가 담당하게 되는데, 각각의 프론트엔드는 사람이 작성한 소스 코드를 LLVM Core가 인식할 수 있는 중간 코드 LLVM IR(LLVM Intermediate Representation)로 컴파일한다. LLVM IR은 .ll 형식을 가진 LLVM 어셈블리(LLVM Assembly)[3]와 .bc 형식을 가진 LLVM 비트코드(LLVM Bitcode), 그리고 .o 형식을 가진 C++ 목적 코드(C++ Object Code)로 분류된다.

이 중에서 LLVM 어셈블리와 LLVM 비트코드는 둘 다 하드웨어 독립적이고 어셈블리어에 가까운 형태를 취하고 있다. 다만 LLVM 어셈블리는 사람이 비교적 읽기 쉬운 원시 코드와 유사한 반면 LLVM 비트코드는 메모리 주소값의 나열로 구성된, 좀 더 최적화가 이루어진 형태의 코드이다. 이러한 LLVM 어셈블리/비트코드는 미들엔드에서 최적화가 한 번 더 진행된 후 백엔드에서 실제 기계어로 바뀌어 실행되는데, 그 번역 방식에는 기계어 인터프리트와 실행이 함께 이루어지는 JIT 컴파일 방식과 미리 기계어로 다 번역해 놓고 실행하는 AOT 컴파일 방식이 모두 존재한다.

이처럼 각각의 기능들이 분리되면 커다란 장점이 생긴다. 예를 들어 C++ 코드를 이용하여 x86-64ARM을 대상으로 바이너리를 만들어야 한다고 가정할 때, 기존의 컴파일러라면 각 아키텍처에 맞는 프론트엔드-미들엔드-백엔드 인프라를 전부 별도로 사용해야 하니 총 2개의 툴체인이 필요하지만, LLVM 툴체인을 사용할 경우 Clang으로 컴파일만 하면 모든 아키텍처에 대한 빌드가 가능해지므로 상당한 편리함을 누릴 수 있다. 윈도우 환경을 정식으로 지원하지 않는 GCC나, 유닉스 계열 운영체제를 정식으로 지원하지 않는 VC++와 달리 두 OS를 모두 지원하는 크로스플랫폼 컴파일러라는 것도 장점이다.

LLVM은 컴파일러 개발자에게도 많은 장점이 있는데, 컴파일러를 만드는 쪽에서 프론트엔드부터 백엔드까지 모든 걸 구현해야 했던 예전과는 달리, 프론트엔드 및 LLVM과의 연결부만 구현하면 LLVM이 코드 최적화와 기계어 생성을 담당해 주기 때문이다. 덕분에 현재 다양한 언어에 대응하는 여러 LLVM 프론트엔드 컴파일러들이 만들어지고 있다.

이름에 Virtual Machine이 들어가기는 하지만, 가상머신은 아니다. LLVM Core는 JVM이나 .NET CLR이 수행하는 메모리 핸들링, 런타임 환경 구축, 리소스 접근 등의 화려한 기능들은 제공하지 않는다. 단지 LLVM IR이라는 중간 코드를 만들어 모듈화된 바이너리 생성을 할 수 있다는 점이 가상머신과 유사할 뿐이며, 이에 따라 LLVM에는 가상머신으로 인해 유발되는 속도 저하 이슈도 없다. 위에서 LLVM이 약자가 아닌 프로젝트의 전체 이름이라고 강조한 것도 이러한 혼동을 방지하기 위함인 듯하다.

3. LLVM 프로젝트

3.1. LLVM Core

LLVM 미들엔드/백엔드를 구성하는 라이브러리. LLVM IR은 특정 아키텍처로부터 독립적이며, 어떤 아키텍처에 설치된 LLVM Core로 빌드했느냐에 따라 타겟 플랫폼이 결정된다. LLVM Core에는 LLVM 비트코드를 실행하는 JIT 컴파일러도 포함되어 있어서 에뮬레이터에서 JIT 컴파일을 구현할 때 많이 쓰인다.

3.2. Clang


파일:나무위키+유도.png  
Clang은(는) 여기로 연결됩니다.
C language에 대한 내용은 C 언어 문서
번 문단을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
참고하십시오.
Clang은 libclang과 그 프론트엔드로 구성된 C[4] , C++, Objective-C용 컴파일러이다. '클랭'이라고 읽으며, 2007년에 첫 등장하였다. LLVM 프로젝트의 메인 프론트엔드라고 할 수 있다. 소스 코드를 LLVM IR로 컴파일하는 역할을 담당한다.

GCC가 GPLv3를 적용한 이후 소스 코드 공개를 꺼리는 기업들이 하나둘씩 LLVM/Clang을 지원하고 있다. LLVM과 Clang은 University of Illinois/NCSA Open Source License(UIUC License)라는 공개 라이선스를 따르며, 이 라이선스는 소스 코드 공개의 의무가 없기 때문이다.[5] FreeBSD는 버전 10에서 GCC를 밀어내고 LLVM/Clang이 디폴트 컴파일러가 되었으며, 애플은 OS X 매버릭스에서 마지막까지 남아있던 LLVM-GCC를 제거한 후 macOS의 기본 컴파일러를 Clang으로 완전히 교체하였다. 때문에 macOS에서는 터미널에서 gcc 커맨드를 사용해도 GCC가 아닌 Clang을 사용해서 컴파일한다. 때문에 macOS의 터미널에서 gcc --version 커맨드를 실행하면 컴파일러 명이 GCC가 아닌 Apple clang으로 표시된다.

윈도우의 경우 두 가지 사용 방법이 존재하는데, 첫번째로 MSVS와 함께 사용하는 방법이다. 이 경우 LLVM 다운로드 페이지에서 Pre-Built Binaries: Clang for Windows를 내려받아 설치하고, clang-cl 컴파일러를 사용하면 된다. clang-cl은 프론트엔드를 Clang으로, 미들엔드-백엔드를 MSVC Build Tool로 활용하는 윈도우용 Clang 구현체이다. 두번째로는 MinGW를 통한 방법이다. MSYS2 mingw-w64에서 pacman -S mingw-w64-x86_64-clang 설치 후 mingw64의 bin을 PATH에 넣고 사용하거나 MSYS Bash를 통해 사용하는 방법이 있다.

첫번째의 경우 런타임 라이브러리는 MSVC 런타임인 msvcp를 사용하게 되며, 후자의 경우 libstdc++ 또는 링커 옵션을 통해 libc++를 사용할 수 있다.

LLVM/Clang은 C++의 새로운 표준인 C++11을 가장 먼저 지원한 컴파일러이다. C++14가 나왔을 때도 마이크로소프트의 MSVC 컴파일러는 C++11조차 제대로 지원하지 않는 등 상대적으로 굼뜬 모습을 보였던 데 비해, Clang은 빠르게 C++14에 대한 Feature Complete를 달성했고 C++17의 기본적인 요소까지 적용했다.

Clang의 일부인 clang-format은 중괄호나 탭과 같은 C++ 소스 코드의 스타일을 일괄적으로 정리하고 유지할 수 있게 해준다.

3.2.1. Swift-Clang

애플iOS에서 사용되는 언어 Swift에 대응하는 프론트엔드 컴파일러. 애플이 기존의 LLVM을 포크해서 만든 Swift-LLVM과 결합되어 돌아간다. 기본적인 구조는 Clang과 유사하지만 스위프트 소스 코드와 LLVM IR 사이에 SIL(Swift Intermediate Language)이라는 또다른 중간 코드가 하나 더 있다. 즉 프론트엔드에서 스위프트 코드는 SIL로 컴파일될 때 한 번, LLVM IR로 컴파일될 때 한 번 해서 총 두 번 최적화되는 것이다.

LLVM/Clang과 마찬가지로 UIUC License를 따른다.

3.2.2. Flang

Fortran에 대응하는 프론트엔드 컴파일러이다. Clang을 기반으로 만들어졌으며 엔비디아에서 지원하고 있다. GCC의 Fortran 컴파일러와 LLVM을 결합하는 DragonEgg 프로젝트가 사실상 중단되어 Fortran 사용자들은 Flang으로 넘어가고 있다.

3.2.3. Kotlin/Native

구글에서 안드로이드의 차세대 공식 언어로 지정한 Kotlin을 기계어로 컴파일하기 위한 Clang 기반의 프론트엔드 컴파일러. 원래 Kotlin은 JVM 언어로서 바이트코드를 생성하여 JVM에 의해 실행되지만, LLVM 미들엔드-백엔드 인프라와 결합된 Kotlin/Native 컴파일러를 사용하면 가상머신에 의한 성능 손실을 없애고 네이티브 바이너리로 직접 컴파일하는 것이 가능해진다.

3.2.4. 그 외

그 외 LLVM을 백엔드로 사용 가능한 언어 목록[6]

3.3. LLDB

LLVM 프론트엔드에 대응하는 디버거이다. GCC의 GDB와 동일한 포지션을 갖고 있으며, 애플 Xcode 5.0 이상의 버전에서 기본 디버거로 사용되고 있다. GDB보다 에러 메시지가 훨씬 간결하면서도 명확하다는 것이 장점. 윈도우 버전은 아직 개발 중이지만, WSL 환경을 이용하면 리눅스용 LLDB를 사용하는 것이 가능하다. 또는 MSYS2 터미널에서 pacman -S mingw-w64-x86_64-lldb 설치를 통해 MinGW 환경에서 사용할 수도 있다.

GDB와 달리 포트란 코드의 디버깅은 지원하지 않는다.

3.4. lld

LLVM을 위한 플랫폼 비종속적인 링커 프로젝트이다.

컴파일 과정은 컴파일러만으로 끝나는 것이 아니며, 생성된 기계어 코드와 사용할 정적 라이브러리를 알맞게 묶어서 실행할 플랫폼의 실행 파일 포맷으로 출력해내야 한다. 특히 리눅스와 윈도우 계열 링커가 전혀 호환이 안 되기 때문에 크로스 컴파일이 힘들어지고, 이를 위해 가능한 한 플랫폼 종속성을 없애는 것이 목적이다.

Clang의 경우 기본적으로 시스템 기본 링커를 사용하기 때문에 lld를 쓰려면 -fuse-ld=lld 옵션을 줄 수 있다.[8] 현재 clang이 어떤 링커를 쓰는지 확인하고 싶다면 컴파일시 -### 플래그를 주면 된다.

현재 ELF, PE, WASM 포맷을 지원한다.
파일:lldperf.png
SSD 드라이버가 탑재된 2소켓 20코어 40스레드 Xeon E5-2680 2.80 GHz 기기 위에서의 벤치마크 결과
기존 GNU ld, gold등 여타 링커들보다 성능이 압도적으로 빠르다. 위 벤치마크는 멀티코어 CPU 기준이지만 --no-threads 옵션으로 강제로 싱글코어 상태로 실행해도 평균적으로 gold보다 두배 이상 빠르다. 멀티코어일 때 최적의 성능이 나오는 이유는 string merging 과정에서의 해시테이블 룩업 로직 때문인데, 기존 단 하나의 테이블을 사용하던 것을 여러 shard로 분배하면서 각 스레드별로 거의 완전한 병렬화(parallelize)를 실행한다. 이는 해시 특성상 별도의 Lock이 필요하지 않기 때문이다.

LTO(link-time-optimization)를 내장 지원하기 때문에 더 빨리 코드를 생성할 수 있으며, clang에 -flto 옵션을 주어서 활성화할 수 있다.

[1] Toolchain. 연쇄적으로 사용되는 소프트웨어 개발 도구들의 집합을 말한다. 컴파일러가 컴파일을 하면 그 결과물인 어셈블리어 파일을 어셈블러가 기계어로 번역하고, 링커가 그렇게 번역된 파일들을 연결하므로 컴파일러-어셈블러-링커는 하나의 툴체인이라고 할 수 있다.[2] 분석, 변환 및 코드 배포를 위한 공통 표현 역할을 하는 여러 새로운 기능이 있는 코드 표현으로 활용됐고, 우리가 알고 있는 이전의 컴파일 방식에서는 사용할 수 없는 기능 조합을 제공하기 위해 사용된다.[3] 공식 레퍼런스 문서에서는 Human-Readable Assembly라고도 표현하고 있다. #[4] LLVM은 C언어에 대해서도 광범위한 유형 정보를 제공한다. 일반적으로 소스 수준 컴파일러의 유형 안전 언어에서만 시도되는 여러 공격적인 변환을 안전하게 수행하는 데 사용될 수 있다.[5] MIT 라이선스BSD 라이선스를 부분적으로 따 와서 합친 형태이다. GPL과도 호환이 가능하다.[6] https://llvm.org[7] https://github.com/ASDAlexander77/TypeScriptCompiler[8] If you don’t want to change the system setting, you can use clang’s -fuse-ld option. In this way, you want to set -fuse-ld=lld to LDFLAGS when building your programs. - https://lld.llvm.org/