View

LLVM 컴파일러

sm_amoled 2024. 7. 12. 15:28
300x250

ㅤ플러터 엔진의 C, C++ 코드는 iOS 환경에서는 LLVM 을 통해 컴파일이 된다고 한다.

읭 LLVM??

LLVM이라는 키워드는 Xcode에서 개발을 진행하면서 종종 봤던 키워드이다. (LLDB도 많이 봤던 것 같다!) 공부를 하다보니 clang 이라는 키워드와 LLVM 이라는 키워드가 자주 나왔는데, 평소에는 자주 지나쳤던 이 이름들에 대해서 한 번 정리해보는 시간을 가지면서, 그 흔적을 여기에 정리해두려고 한다 :)


LLVM 이란?

LLVM  컴파일러와 툴체인의 집합이며, 두문자가 아니라, LLVM 자체가 그냥 프로젝트의 이름이다. (오호) 물론 처음에는 low-level virtual machine 이라는 이름에서 출발했지만, 기능이 확장하면서 풀네임이 그 기능을 다 표현해내지 못하면서 두문자만 남겼다고 한다.

LLVM은 쉽게 말하자면, 재사용을 위한 모듈식 컴파일러라고 보면 된다. [번역 대상 언어 - 타겟 아키텍처] 가 이미 정해져있는 컴파일러의 경우에는 컴파일하는 언어와, 머신코드를 실행할 아키텍처의 종류에 따라 언어 수 X 아키텍처 수 만큼의 컴파일러가 별도로 필요하다. 이러한 비효율성을 줄이기 위해서, 번역 대상 언어 모듈과 티겟 아키텍처에 대한 모듈을 분리하는 방식으로 설계된 컴파일러가 LLVM 이다.

즉, 여러 언어와 여러 아키텍처의 조합에 적합하게 파일을 컴파일 할 수 있는 재사용성이 높은 컴파일러라고 보면 된다 :)

이 구조를 위해 LLVM은 크게 3가지 파트—프론트엔드, 미들엔드, 백엔드—로 구분된다.

LLVM의 구성

LLVM은 프론트, 미들, 백엔드로 구성되어 있다. 프론트엔드는 ‘어떤 언어를 변환할 것인가’ 를 결정하는 모듈이고 백엔드는 ‘어떤 아키텍처로 변환할 것인가’를 결정하는 모듈이다. 미들엔드는 프론트와 백의 모듈 종류에 상관없이 주어진 입력에 최적화를 수행하는 부분이라고 보면 된다.

1. 프론트엔드

LLVM 프론트엔드에서는 번역할 코드를 LLVM Intermediate Representation 으로 변환하는 작업을 수행한다.

각각의 언어를 LLVM을 위한 중간 언어 단계인 LLVM IR로 변환한다. 이 LLVM IR은 프로그래밍 언어와 머신 아키텍처로부터 컴파일러 로직 자체를 분리시키기 위해 사용하는데, 특정 언어나 머신 아키텍처에 종속된 형태가 아니라 완전히 독립적이기 때문에 어떤 언어에서든 IR 로 변환이 가능하고, 또 어떤 아키텍처로든 IR 로부터 변환이 가능하도록 해준다. 어떻게 보면 LLVM 의 가장 핵심이 이 LLVM IR 로 변환하여 처리하는 것이라고 볼 수 있을 것 같다.

중간 단계 언어는 LLVM 어셈블리 LLVM 비트코드가 있는데, AOT 컴파일을 위해서는 어셈블리를, JIT 컴파일을 위해서는 비트코드를 사용하는 것으로 생각된다. (← 요 부분은 팩트체크가 필요하다)

GPT의 답변

 LLVM 어셈블리는 사람이 읽을 수 있는 텍스트 형식으로, 주로 디버깅, 테스트, 교육 및 연구 목적으로 사용됩니다.

 LLVM 비트코드는 바이너리 형식으로, 컴파일 파이프라인에서의 효율적인 데이터 처리, 라이브러리 배포, 모듈화, 배포 후 최적화 및 호환성 유지 등의 목적으로 사용됩니다.

각 프로그래밍 언어의 코드를 IR로 변환하는 과정에서는 언어에 따라 각자의 컴파일러를 사용하게 된다. C 언어 계열의 경우에는 GCC 혹은 clang을, Swift 언어의 경우에는 swiftc (swift compiler) 등을 적용해주게 된다.

이 부분에서 프로그래밍 언어론이나 컴파일러에서 배웠던 것 같은 Token 분리, AST 설계, Parser를 거쳐서 IR로 변환하는 과정이 진행된다.

Clang

clang [클랭] 은 맥OS 진영에서 기본 컴파일러로 탑재가 되어있는 C 계열 언어 (C, C++, Objective-C, Objective-C++ 등) 의 컴파일러이다. GCC 보다 clang이 성능적으로는 조금 더 최적화를 잘 할 수 있는 컴파일러이고, 좀 더 최신 버전의 언어와 문법들을 빠르게 지원하도록 업데이트를 잘 해준다는 차이가 있다고는 하지만 사실 요 부분은 시간의 차이일 뿐 둘 다 동일하게 C 계열의 언어들을 컴파일 한다는 사실은 동일하다고 생각된다.

다만 두 컴파일러의 라이센스 조항이 다른데, 나는 GCC와 clang 중 하나를 선택하는 기준이 요 라이센스일 거라는 생각이 들었다. GCC의 경우 GPL 라이센스를 따르면서 소스코드를 공개해야 하는 의무가 있지만, clang은 아파치 라이센스를 따르기 때문에 소스코드를 공개할 의무가 없다.

그래서 대표적으로 애플 같이 소스코드를 공개하기 꺼려하는 곳에서는 clang을 채택하고 있다. 실제로 Xcode 설정에서 C 계열 언어들의 컴파일러를 선택할 때에도 clang이 먼저 보여지고, 내 맥북 터미널에서 gcc 의 버전을 찍어봤을 때에도 GCC 명령어를 알아서 clang으로 바꿔서 처리해버리는 것을 확인할 수가 있다!

clang이 요런 녀석이였고만!

2. 미들엔드

LLVM의 미들엔드에서는 프론트엔드에서 넘겨온 LLVM IR을 최적화하는 작업을 진행한다.

어차피 IR 자체가 언어와도 상관없고 아키텍처와도 상관 없기 때문에, 특별한 환경적인 제약 없이 마음대로 IR을 최적화시킬 수 있다.

IR의 최적화를 완료하면, 이를 다시 백엔드로 넘겨준다.

3. 백엔드

백엔드에서는 미들엔드로부터 전달받은 IR을 각 아키텍처에 맞는 머신코드로 변환하는 작업을 처리한다. 보통은 해당 LLVM이 설치되어있는 환경(아키텍처)에 맞는 머신코드로 변환해서 전달해주는 것으로 보인다. 여기 백엔드 부분에 어떤 아키텍처에 대한 모듈을 끼워넣냐에 따라 LLVM 이 컴파일하여 만들어내는 머신코드가 결정되게 된다.

물론 최적화된 LLVM IR 이라도 쌩으로 아키텍처에 변환해서 박아넣을 수 없기 때문에, 백엔드 컴파일러에서도 레지스터 최적화나 명령어 실행 순서 조정같은 아키텍처에 적합한 최적화 과정을 진행해준다.

LLVM의 백엔드 (LLVM Core) 에는 IR 을 기계어로 번역해주는 역할도 수행하지만, LLVM Bitcode (LLVM 버전 bytecode 같은 것) 을 실행할 수 있는 JIT 컴파일러도 포함되어있어서 플러터처럼 에뮬레이터 핫 리로딩 기능도 가능하다.

320x100
Share Link
reply
반응형
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30