박종훈 기술블로그
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: 메모리에 접근하는 노드다. LoadNodeStoreNode가 대표적이며, is_Mem() 메서드로 확인할 수 있다.
  • CallNode: 함수 호출을 나타내는 노드다. is_Call() 메서드로 확인할 수 있다.

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

정리

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

categories: 개발

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