본문 바로가기

Computer Science

컴파일이란 무엇이며, 자바스크립트는 인터프리터 언어인가?

컴파일이란 무엇인가?

컴파일은 우리가 작성한 소스 코드를 오브젝트 코드로 변환시키는 과정이다. 인간이 알아듣기 쉬운 프로그래밍 언어인 High Level Language를 기계가 알아들을 수 있는 0과 1로 이루어진 기계 언어인 Low Level Language로 변환시키는 것이다. 

 

이 과정은 네 단계로 세분화해볼 수 있다.

1. 전처리기

C언어로 예를 들자면, #으로 시작되는 소스코드를 처리하는 단계이다.

stdio.h와 같은 헤더 파일을 불러와 코드 상으로 필요한 내용으로 채워주고 define으로 먼저 정의된 상수를 symbol table에 저장하는 등 매크로를 확장한다.

 

2. 컴파일

High Level Language인 소스코드를 기계언어에 가까운 Low Level Launguage인 어셈블리 언어로 변환한다.

 

3. 어셈블러

여기서 의문이 들었다. 어셈블러는 왜 필요한걸까? 그냥 바로 0과 1로 바꿔버리면 안되나? 

이유를 한 줄 요약하자면 사람이 기계어를 편하게 이해하기 위해서 이다. 

결국 컴퓨팅하는 주체는 CPU이므로 소스코드를 아무리 잘 작성했더라도 CPU 입장에서도 그게 잘 작성된 코드인지 들어봐야한다. 어셈블리어는 인간이 이해할 수 있는 기계 언어에 가장 가까운 언어로, 컴퓨터가 연산하는 블랙박스 안을 들여다볼 수 있는 창구의 역할을 한다. 따라서 컴퓨터의 동작 방식을 이해하고 더 가까이서 문제를 해결하기 위해 어셈블리어로 중간 변환 과정을 거친다.

실제로 어셈블리 언어에는 집합, 배열, 객체와 같은 개념이 없고 모두 정수(int)로 변환된다.

 

4. 링커

만약 프로그램이 여러개의 파일로 이루어져있다면 하나의 오브젝트 파일로 이어주고 라이브러리들을 연결하는 링크 단계가 필요하다.

 


 

컴파일 언어와 인터프리터 언어의 차이점

컴파일 언어(C, C++, Java, TypeScript 등)는 실행에 들어가기에 앞서 기계 언어로 미리 바꿔두기 때문에, 런타임 환경에서 빠르게 동작한다. 컴파일하는 시간은 걸리지만 그 과정에서 Syntax Error나 Type Error를 감지하기때문에 실제 동작에서 예상치 못한 에러를 마주할 일이 적어진다.

반면에 인터프리터 언어(JavaScript, Python, R 등)실행과 동시에 한 줄씩 중간 언어로 해석한 다음 실행한다. 별도의 컴파일 과정 없이 High Level Program을 바로 실행시킬 수 있어 변경사항을 빠르게 테스트해보기 용이하고, 대화식(Jupyter notebook, Colab 등)으로도 사용 가능하다.

 

자바스크립트는 인터프리터 언어인가?

TL;DR
때에 따라 다르다. 현 시점에서의 자바스크립트는 실질적으로 컴파일이 되지만 편의 및 문맥상 인터프리터 언어로 분류된다.
모던 자바스크립트 컴파일러는 거의 런타임 내에서 빠르게 컴파일(JITC, Just-In-Time Compilation)을 수행한다.

 

자바스크립트의 목적은 웹 문서 구조를 동적으로 나타내기 위함이다. 따라서 그 목적에 맞게 가벼운 인터프리터 언어로 만들어졌다.

하지만 유저 인터렉션이 늘어나 점점 그 양이 방대해졌고, 3초 이상의 로딩은 참을 수 없을 정도로 유저 경험을 중요시하게 되었다.

 

인터프리터 언어 자바스크립트를 실행하는 환경인 V8 엔진은 2009년 구글이 출시한 세미 인터프리터이다.

세미 인터프리터라는 말은 내가 붙인 이름이지만 필요에 따라 컴파일 과정을 거쳐 자바스크립트 실행 성능을 높이는 방식을 사용한다.

그 과정을 조금 더 자세히 들여다보자.

 

출처 Inside the JavaScript Engine, Leonardo Freitas

자바스크립트 코드를 받은 뒤, 토큰이라는 단위로 잘게 쪼갠 뒤 의미상의 구조로 트리 형태로 정리되는 Parse와 AST(Abstract Syntax Tree) 과정을 거친다.

parser에 대한 더 자세한 이야기는 여기, AST에 대한 이야기는 여기를 더 참고하면 좋겠다!

 

이후 인터프리터에서 코드를 해석하면서 실행하는데, 중간 언어라고 언급했던 ByteCode 형태로 빠르게 변환한다.

그리고 Unoptimize 상태의 이 과정을 프로파일러가 모니터링하며 최적화할 수 있어보이는 코드들을 빠르게 컴파일하는 Just-In-Time 컴파일러로 넘겨 효율적으로 실행하도록 돕는다. 

이때 Deoptimize하는 경우도 있는데, 프로파일러의 판단(이 코드 컴파일하는게 낫겠네!)이 틀렸을 수도 있기 때문에 컴파일하는 비용을 다시 줄이기 위함이다.

 

어쨌든 인터프리터 언어이긴 하네! 왜 인터프리팅 방식을 고집할까?

여러 종류의 브라우저가 공존하고 모든 환경에서 통일되게 사용할 수 있는 기계어가 필요하기 때문인데, 실제로 2017년에 발표된 WebAssembly 언어가 계속해서 이슈화되고 있는 것 같다. 아직 TypeScript랑도 간신히 통성명한 상황이라 당장 공부해보기엔 조금 거리감이 있지만, 컴파일 단계와 세미 인터프리터 언어로서의 자바스크립트에 대해 공부한 뒤라 어떤 점에서 주목받는지 쉽게 와닿는 것 같다.

깊이는 아니더라도 WASM의 컨셉과 적용 방식에 대해서도 간단히 알아보고 정리해봐야겠다.

 

 

링크

Inside The JavaScript Engine

JavaScript, 인터프리터 언어일까?