컴파일러 동작에서 대해 얕지만 넓게 알아보자 🤔

안녕하세요! 두두코딩 입니다 ✋
오늘은 GCC 와 LLVM에 대해 알아보겠습니다.

🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!

궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇

Intro

Chromium에서 최근 Clang에 대한 도입(Clang compile) 에 대한 이야기가 많이 있었다. Clang이 무엇인지 궁금해 찾아보며 공부하는 과정에서 “컴파일러의 동작과정”, “Clang 과 GCC의 차이점” 들에 대해 이야한 부분을 정리하고자 한다.

해당 포스팅의 내용의 중요 흐름은 3가지 정도로 요약할 수 있다.

🌱 컴파일러 동작 과정

🌱 GCC 컴파일러란?

🌱 LLVM 컴파일러란?

얕지만 넓게 컴파일러에 대한 이해도를 올려보자🎶

프로그램 생성 과정

우리는 프로그램을 만들기 위해 프로그래밍 언어를 활용해 “소스코드” 를 작성한다. 이후 “소스코드”를 4가지 과정을 거쳐 실행가능한 프로그램을 만든다.

아래는 일련의 과정을 도식화한 그림과 설명이다.

compiler0

  1. 전처리 과정 : 전처리기를 통해 헤더파일 혹은 메크로를 치환하여, 결과를 .i 파일확장자 명으로 저장한다.
  2. 컴파일 과정 : 컴파일러는 저수준의 언어인 어셈블리어로 컴파일 후, 결과를 .s 파일확장자 명으로 저장한다.
  3. 어셈블 과정 : 어셈블러는 저수준의 언어인 어셈블리어를 링커가 읽을 수 있는 목적파일로 변환해, 결과를 .o 파일확장자 명으로 저장한다.
  4. 링킹 과정 : 링커는 파일 확장자가 .o인 목적파일들을 하나로 묶어 실행파일 (a.out)로 생성한다.

위 4가지 동작을 Compiler Collection (컴파일러 모음집)이라고 부르며, 잘 알려진 모음집으로 GCC (GNU Compiler Collection) 과 LLVM (Low Level Virtual Machine)이 있다.

해당 포스팅에서는 컴파일 과정을 좀 더 자세하게 알아보고, 가장 핫하게 사용중인 컴파일러 모음집의 2가지 컴파일러 GCC 모음집의 GCC Compiler 와 LLVM 모음집의 Clang & LLVM 에 대해 알아본다.

컴파일러의 동작과정

컴파일 과정을 요약하자면 아래와 같다. 해당 그림은 GCC Compiler 기준으로 작성되었으며 LLVM 동작도 이와 비슷하다.

compiler1

그림과 같이 컴파일 과정은 중간단계인 Middle-end 단계가 있지만 Web Server와 유사하게 Front-end / Back-end로 나눠져있다.

🌱 컴파일 과정 1. Front-end

Front-end 같은 경우 전처리기 동작을 통해 변환된 소스코드가 올바르게 작성되었는지 확인하는 과정이다. 총 4가지 과정을 거치게 되고, 올바르다고 판단한 소스코드를 트리형태 (GIMPLE)로 변환한다.

compiler2

위의 그림과 같이, 4가지 동작과정의 구체적인 기능은 아래와 같다.

  1. 어휘 분석 : 소스코드를 의미있는 최소단위인 토큰단위로 나눈다.
  2. 구문 분석 : 문법적 오류를 검출하기 위해, 토큰을 파스 트리 형태로 변환한다.
  3. 의미 분석 : 파스 트리를 순회하며 의미상 오류를 검출한다. 보통 함수의 매개변수를 잘못사용하거나 자료형 타입을 잘못사용하는 것들이 검출된다.
  4. 중간 표현 생성 : 언어의 독립 특성을 제공하는 GIMPLE Tree를 생성한다.

🌱 컴파일 과정 2. Middle-end

Middle-end 부분에서는 Front-end 단에서 도출된 GIMPLE Tree를 SSA 형태로 변환한 후 아키텍쳐 의존성을 없애는 작업을 진행한다. 이후 고급언어와 어셈블리어의 중간단계인 RTL(Register Transfer Language)을 생성한다.

compiler3

해당 단계에서 optimization이 일어나며, 컴파일러 모음집에 따라 성능차이가 발생할 수 있는 부분이라고 생각한다.

Tip SSA (Static single-assignment)란? 데이터 흐름분석과 코드최적화를 위해 사용되는 중간표현으로 생각하면 쉽다. 보통 정적으로 값과 데이터 타입을 결정하기 위해 변수의 배정 또는 흐름을 분리하는 형태에 사용된다.

🌱 컴파일 과정 3. Back-end

아래의 그림과 같이, Back-end에서는 2가지 최적화가 일어난다.

compiler4

  1. RTL optimization에 의한 RTL 최적화 (아키텍쳐 의존성 없음)
  2. 아키텍쳐 별 코드 최적화

위 최적화가 완료되면 Code generator를 통해 .s 확장자를 가진 어셈블리 코드를 만든다.

컴파일의 기본 골격은 동일하며, 컴파일러 모음집 별 최적화 혹은 아키텍쳐 확장성 등에 별로 다르게 구성될 수 있다.

그렇다면, 가장 많이 사용되고 언급되는 GCC 컴파일러와 LLVM에 대해 알아보자.

GCC 컴파일러

GCC란 GNU Compiler Collection의 약자이다. 원래는 C 언어를 컴파일할 수 있는 컴파일러만 존재해 GNU C Compiler 라는 이름이 모음집으로 확장(java, C++)되어 사용되고 있다.

Tip GCC 컴파일러로 다른 언어를 사용하기 위해서는 라이브러리를 추가해야된다. 아래의 버튼을 클릭해 C++을 컴파일 하기 위한 명령어를 참고해보도록 하자.

# -lstdc++ 이라는 옵션을 추가해야됨.
$ gcc -o "filename" "filename.cpp" -lstdc++

GCC는 컴파일러 모음집으로 단순한 컴파일 과정을 넘어 전처리 동작, 어셈블 동작, 링킹 동작을 같이 수행해 binary file 즉, 실행가능한 파일을 만드는 역할을 한다.

GCC 같은 경우 Front-end 부터 Back-end까지 모두 자체적으로 개발해 제공되고 있으며, GPLv3 인증으로 모든 소스가 공개되어져 있다. 해당 컴파일러 모음집을 사용할 경우 사용된 소스를 무조건 공개해야되는 의무를 가지게 된다. 참고

LLVM

통상적으로 LLVM은 컴파일러라고 불리지 않는다. GCC 같은 경우 원래 컴파일을 전용으로 만들어진 이름 (GNU C Compiler)이라 GCC 컴파일러라고 부르지만, LLVM 같은 경우 하나의 프로젝트 이름 즉, 컴파일을 가능하게 하는 모음집 정도로 생각하고 있기 때문에 보통 LLVM 프로젝트라고 부른다.

LLVM 프로젝트의 목표는 GNU 컴파일러를 대체하는 것이다.

왜 GNU 공짜인데 GNU 쓰면되지.. 대체하는걸까? 🤔

😎 대체하는 이유는 “철학” 때문이라고 생각한다. GNU가 GCC 4.2 부터 GPLv3라는 라이센스를 채택했다. 따라서, GCC를 사용하고 있는 모든 SW들은 소스코드를 공개해야되는데, 대형 SW 회사 (e.g. Apple 등)는 해당 의무로 상업적 손해를 받기가 싫었던 것이다. 따라서, 비슷한 대체제가 필요했는데.. 거기서 사용된 Compiler가 LLVM이다. 지금은 Apple 주도하에 Google등과 같은 대기업들이 지원을 해주고 있으며 Apple 제품에서는 LLVM을 사용하고 있다. 자세한 내용은 여기를 클릭해서 읽어보도록 하자!

LLVM 같은 경우 BSD라이센스를 사용하고 있으며, 해당 라이센스 같은 경우 상업적으로 사용하더라도 소스코드를 공개할 필요는 없으며 사용에 대한 출처표기만 하면 된다.

GCC 컴파일러와 내부적 최적화등에서는 차이가 있을 수 있지만, 기본 골격 구조는 동일하다.

내부적으로 LLVM 같은 경우 컴파일 단계마다 서브프로젝트를 두고 운영 중인데 간단하게 언급하면 다음과 같다.

  1. Front-end = Clang 프로젝트 (드디어 Clang이라는 이름을 만났다.. 😌)
  2. Middle-end = IR (고급언어와 어셈블러의 중간단계)
  3. Back-end = LLVM-core 프로젝트

아래의 그림을 참고하자!

LLVM

해당 포스팅에서는 LLVM이 3단계로 나눠지고, 컴파일러의 골격 구조는 동일하다 정도로 정리하고 넘어간다. LLVM의 좀 더 깊이 있는 내용을 원할 경우 다음포스팅이나 문서를 참고해주기 바란다.

🌱 Clang

Clang을 통해 궁금증이 시작되었기 때문에 LLVM에서 Clang 부분을 추가로 조사해보았다.

Clang은 LLVM front-end 를 담당하고 있는 프로젝트이다.

위에서 언급한 것과 같이, GCC가 4.2 version으로 올라가면서 GPLv3를 채택하기로 했다. 따라서, 상업적으로 GCC를 사용하던 회사들은 GCC 대체제가 필요했고, 당시 Middle-end와 Back-end 를 주력으로 개발하는 LLVM-core 프로젝트를 확장시키고자 했다.

LLVM 프로젝트에서 GCC Front-end를 대체재가 필요했고, 이를 위해 Clang이라는 LLVM front-end compiler가 탄생했다.

GCC를 대체하기 위해 탄생한 LLVM의 Clang 프로젝트는 현재 GCC의 모든 옵션을 대체한다고 봐도 과언이 아니다.

또한 Compiler의 Front-end를 담당하는 만큼 “소스코드” 받아들이는 도입부로 다양한 언어 (C++, Swift 등)를 채택하기 위해 노력하고 있다. 사용 방법도 GCC와 비슷하며 아래의 명령어를 통해 컴파일이 가능하다.

# clang compiler
$ clang <source file> [-o output path]

GCC 프로젝트(g++)보다 C++ 버전을 가장 빠르게 업그레이드 하는 것으로 유명하다.

Chromium에서 Clang을 채택하려고 하는 이유는 C++ 친화적이기 때문이아닐까? 라는 조심스러운 생각을 해본다.

Reference