뜬금없이 'Python은 인터프리터 언어인데, 어떻게 컴파일 언어인 C 위에서 돌아가는거지?' 라는 의문이 생겨서 작성하는 글
알고 보니 인터프리터라고 단순히 바로 실행되는 것이 아니라, 바이트코드까지의 컴파일 후 런타임에서 interpret 된다.
Python Source (.py)
↓ [컴파일 단계]
Bytecode (.pyc)
↓ [실행 단계]
Python Virtual Machine (PVM, C 기반)
↓
C 레벨에서 실행
.py→ 사람이 작성한 코드.pyc→ Python이 내부적으로 생성한 바이트코드 파일- PVM → C로 작성된 Python Virtual Machine(PVM) 으로, 이 바이트코드를 한 줄씩 해석(interpret)하며 실행한다.
즉, Python도 “컴파일 → 실행” 단계를 거치지만,
결과물이 기계어가 아닌 가상머신용 코드(바이트코드) 라는 점이 다르다.
이런 구조 덕분에 Python은 실행 중에도 코드를 수정하거나
새로운 객체를 동적으로 만들 수 있는 런타임 유연성을 갖게 된다.
Swift는 완전한 컴파일 언어다.
모든 코드는 빌드 시점에 LLVM 을 거쳐 기계어로 변환(AOT, Ahead-of-Time)되어 실행된다.
Swift Source (.swift)
↓ [컴파일 단계]
Swift Intermediate Representation (.swiftmodule, SIL)
↓ [LLVM 변환 단계]
LLVM IR (.ll / .bc)
↓ [코드 생성 단계]
Object File (.o)
↓ [링크 단계]
Executable Binary (.app / Mach-O)
↓
운영체제에서 직접 실행
.swift→ 사람이 작성한 Swift 소스 코드.swiftmodule→ Swift 컴파일러가 생성한 모듈 정보 및 중간 표현(SIL 기반).ll/.bc→ LLVM이 생성한 중간 코드(IR, Intermediate Representation).o→ LLVM 백엔드가 생성한 아키텍처별 기계어(Object File).app(또는 실행 파일) → 링커가 여러 오브젝트 파일을 묶어 만든 최종 실행 바이너리(Mach-O 포맷)
Swift 컴파일러는 LLVM을 사용해 코드 최적화와 기계어 변환을 담당한다.
결과물은 완전한 Native Binary로 런타임 해석 과정이 없고, 따라서 실행 중 코드 수정이나 Hot Reload 같은 동적 기능은 지원되지 않는다.
LLVM은 “언어와 하드웨어 사이의 중간 계층”이다.
즉, 여러 언어가 공통으로 사용할 수 있는 컴파일 백엔드를 제공한다.
| 계층 | 역할 | 예시 |
|---|---|---|
| 프론트엔드 (Frontend) | 언어별 문법 분석, AST 생성 | Swift Compiler, Clang(C/C++) |
| 중간 계층 (IR) | 공통 중간 표현 (Intermediate Representation) | LLVM IR |
| 백엔드 (Backend) | CPU별 코드 생성 (ARM, x86 등) | LLVM Codegen |
LLVM은 Swift뿐 아니라
C, C++, Rust, Kotlin/Native, Dart(AOT 빌드 시) 등에서도 사용된다.
LLVM은 컴파일에 필요한 여러 기술과 도구를 모아놓은 컴파일러 프레임워크 프로젝트입니다. LLVM 컴파일러 인프라는 3단계 구조로 되어 있는데요. 각각 구문 분석과 최적화, 기계어(machine code) 생성 역할을 수행하고 있습니다. 이렇게 잘 구조화되어 있는 덕분에 필요한 기능을 컴파일러에 쉽게 추가할 수 있습니다.
- Line Engineering: 오크(ORK) - 난독화 컴파일러 도구 1편
여기까지 공부하고 나니 Flutter(Dart)가 Android/iOS 위에서 어떻게 동작하는지 궁금해져서, 다음 글 에서 Flutter에 대해서도 알아볼 예정이다.