![[2. Introduction to Compiler.pdf]] # 왜 컴파일러가 필요할까요? ## 소스코드 vs 기계어 컴퓨터가 실행할 수 있도록 machine code로 변경해주는 것 == 컴파일러 (자동 번역) # 컴파일러는 무엇인가요? ## 컴파일러 (Complier) 한 프로그래밍 언어 -> 번역 -> 다른 언어의 프로그램 - 일반적으로 High-Level -> Low-level - *c.f* Transpiler (TS > JS 같은 거) ## 인터프리터 (Interpreter) 프로그램을 직접 실행해 결과를 출력하는 프로그램 (저번에 F# 써봤던거, 파이썬 인터프리터) - 컴파일 된 코드보다 느림... 다른 프로그램이 아니라 '결과(Execution Result)' 나옴 - *e.g* REPL 보통 컴파일 방식으로 실행파일 만들어서 실행하거나, 인터프리터로 함. 둘 중 하나로 많이 함. 자바 같은 경우는 두 가지 다 사용 ## Java - Java 소스코드 -> Java 바이트코드 [컴파일러] - Java 바이트코드 -> 실행 결과: 인터프리터 또는 JIT 컴파일러 - *c.f* AOT (Ahead of Time) 컴파일러 / JIT (Just in Time) 컴파일러 ## 컴파일러의 기본 원칙 1. 정확성 Correctness 2. 개선 Improvement (속도 향상? 크기 줄이는 것? 어떤 measurement든) 3. 정확성 >>>>>>> 성능 ## 올바른 /위험한 최적화 코드 위험한 거: while(true) 이건 걍 개어이없네 올바른 거: same_value로 빼기 # 어떻게 코드를 번역하나요? ## 가장 단순한 구조 - 하나의 구성 요소 안에서 처리 - 각 언어 → 각 타겟 아키텍처마다 컴파일러를 따로 만들어야 - n x m ## 기본적 2단계 구조 - 구성 - Front-End: 입력 언어 이해, 중간 언어 IR로 변환 - Back-End: IR을 출력 기계어로 변환 - 장점 - 언어에 따른 모듈 분리 - 다양한 언어/아키텍처로 확장 가능 (입력 n, 출력 m개일 때 n + m개의 구현 필요) ## 새로운 3단계 구조 - 구성 - Front-End: Source -> IR - Middle-End: IR -> IR (IR 분석 & 최적화) - 더이상 번역만 중요한 게 아니고, '성능'도 중요해졌기 때문. - Back-End: IR -> Target ## 컴파일 과정 예시: 영어 to 한국어 - Front-End: 영어의 이해 - Middle-End: 내용 분석 (한국어 특화 최적화) - Back-End: 한글로 변환 # Front-End에선 무엇을 하나요? ## 입력 언어를 이해하는 과정 - 이해 - 검사 - 변환 ## 1. Lexer: Source → Tokens [어휘 분석기] - 문자열을 토큰(Token)의 나열로 변환 (공백, 주석 제거) - 오토마타 이론 기반 ## 2. Parser: Tokens → AST(Abstract Syntax Tree) [구문 분석기] - 왜? C라는 언어의 특징을 아직 토큰들이 많이 갖고 있음. 언어에 특화된 형태를 줄여야 함. 좀 더 추상적 형태로 변환해줌. - 오토마타 이론 기반 ## 3. Semantic Analyzer: AST → AST [의미 분석기] - 만들어진 AST가 올바른 의미를 표현하고 있는지 검사 (타입 분석, 변수 선언 검사) - 자바 int a = "김겨레"; 이지랄했다면? - Jack is at school. "Her" homework... 어쩌고 - 프로그래밍 언어론 기반 # Middle-End에선 무엇을 하나요? ## IR을 변환하는 과정 - 분석 - 최적화 - 프로그래밍 언어론 기반 Optimizer 여러 개가 직렬로 이어져있음. 굉장히 중요하고 어려운 내용이다 ^^ 최적화 예시가 왜 더 나은 예시인가? * 1) 특별한 레지스터들을 갖고 있는 경우가 있다. read-only... 그런 걸 사용해서 구현하게 되면 연산 속도가 더 빨라질 수 있음. * 2) 나중에 결국 기계어로 번역이 될 텐데 변수 - 레지스터 대응되거나 메모리 변수와 대응됨. B가 메모리에 있다고 치면, 그 과정에는 B에서 읽어서... 다시 a에 집어넣는 과정... 레지스터에서 읽기와 메모리에서 읽기는 비용 차이가 많이 남. # Back-End에선 무엇을 하나요? ## 출력 언어를 생성하는 과정 - IR 기반으로 출력 언어 (CPU 아키텍처) 에 맞는 코드 생성 ## 1. Instruction Selector 명령어 선택기: IR → Pseudo Target Program - 레지스터는 정해지지 않은 상태. 트리로 사용. - 알고리즘 기반 ## 2. Scheduler 명령어 순서 결정: Pseudo TP → Pseudo TP ## 3. Register Allocator 레지스터 할당기: Pseudo TP → Target Program - 타겟 명령어의 가상 레지스터에 물리 레지스터 할당 - 어떤 두 레지스터가 동시에 살아있는지?... 를 뭐 그래프로 판단하고?... 이런 문제를 알고리즘으로 해결했대; - 알고리즘 이론 기반 # 우리는 왜 컴파일러를 배우는가? - 컴파일러는 모든 이론의 집합체. - 많은 곳에 사용됨 - 컴파일러를 알면 좋은 코드를 작성할 수 있음~ (컴파일러가 뭘 하는지 알아야 함!)