박종훈 기술블로그
close menu

OpenJDK C2 컴파일러의 노드 클래스 이해하기

들어가며

Java 프로그램을 실행하면 JVM 내부에서는 여러 단계의 컴파일이 일어난다. 그중 C2 컴파일러는 자주 실행되는 코드(“핫 코드”)를 고도로 최적화된 기계어로 변환하는 역할을 한다.

이 글에서는 C2 컴파일러가 코드를 최적화할 때 사용하는 노드(Node) 클래스가 무엇인지, 어떤 계층 구조를 가지는지를 초보자 눈높이에서 설명해본다.

노드(Node)란?

C2 컴파일러는 Java 바이트코드를 바로 기계어로 번역하지 않는다. 대신 먼저 코드를 Ideal Graph라는 중간 표현(IR, Intermediate Representation)으로 변환한다.

이 Ideal Graph는 노드(Node)들의 네트워크로 구성된다. 각 노드는 하나의 연산을 나타낸다.

예를 들어 다음과 같은 Java 코드가 있다고 해보자.

int result = a + b;

이 코드는 내부적으로 대략 이런 노드들로 표현된다.

LoadNode(a) ─┐
             ├─ AddNode ─── StoreNode(result)
LoadNode(b) ─┘
  • LoadNode: 메모리에서 값을 읽어오는 노드
  • AddNode: 두 값을 더하는 노드
  • StoreNode: 결과를 메모리에 저장하는 노드

이렇게 모든 연산이 노드로 표현되고 노드들이 서로 연결되어 하나의 그래프를 형성한다.

함수 호출도 마찬가지다. 예를 들어 다음과 같은 코드가 있다고 해보자.

int len = str.length();

이 코드는 대략 이런 노드들로 표현된다.

LoadNode(str) ─── CallNode(String::length) ─── StoreNode(len)
  • CallNode: 메서드 호출을 나타내는 노드

산술 연산은 AddNode 같은 단순한 노드로 표현되지만 메서드 호출은 CallNode로 표현된다. CallNode는 호출 규약, 예외 처리, 메모리 부수효과 등 훨씬 많은 정보를 다뤄야 하기 때문에 별도의 노드 계층으로 분류된다.

노드 클래스의 계층 구조

C2 컴파일러에는 수백 가지의 노드 타입이 있다. 이것들은 객체 지향적으로 잘 정리된 클래스 계층 구조를 이룬다.

Node                        ← 모든 노드의 최상위 클래스
├── MemNode                 ← 메모리에 접근하는 노드들
│   ├── LoadNode            ← 메모리에서 값을 읽는 노드
│   │   ├── LoadFNode       ← float 값을 읽는 노드
│   │   ├── LoadDNode       ← double 값을 읽는 노드
│   │   ├── LoadINode       ← int 값을 읽는 노드
│   │   └── ...
│   └── StoreNode           ← 메모리에 값을 쓰는 노드
│       ├── StoreFNode      ← float 값을 쓰는 노드
│       ├── StoreDNode      ← double 값을 쓰는 노드
│       └── ...
├── AddNode                 ← 덧셈 노드
│   ├── AddFNode            ← float 덧셈
│   └── AddINode            ← int 덧셈
├── MulNode                 ← 곱셈 노드
├── CmpNode                 ← 비교 노드
├── CallNode                ← 함수 호출 노드
└── ...

핵심 포인트: MemNode와 CallNode

이 계층 구조에서 특히 중요한 두 가지 노드 분류가 있다.

  • MemNode: 메모리에 접근하는 노드다. LoadNode와 StoreNode가 대표적이며 is_Mem() 메서드로 확인할 수 있다.
  • CallNode: 함수 호출을 나타내는 노드다. is_Call() 메서드로 확인할 수 있다.

반면에, AddFNode(float 덧셈)나 CmpFNode(float 비교) 같은 노드들은 메모리에 직접 접근하지 않는 순수 연산 노드이므로 MemNode도 CallNode도 아니다.

정리

  • C2 컴파일러는 Java 코드를 노드(Node)로 구성된 그래프로 표현한다.
  • 노드는 계층 구조를 가지며 특히 MemNode(메모리 접근)와 CallNode(함수 호출)가 중요한 분류다.
  • MemNode를 상속하는 노드는 메모리에 접근하므로 별도의 처리가 필요하고, 순수 연산 노드(AddF, CmpF 등)는 메모리에 접근하지 않으므로 상대적으로 단순하게 다뤄진다.

categories: 개발

tags: OpenJDK , JVM , C2 , JIT , 컴파일러 , 노드