# 루비를 기계어로 컴파일하기

> Clean Markdown view of GeekNews topic #24452. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=24452](https://news.hada.io/topic?id=24452)
- GeekNews Markdown: [https://news.hada.io/topic/24452.md](https://news.hada.io/topic/24452.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-11-19T04:33:05+09:00
- Updated: 2025-11-19T04:33:05+09:00
- Original source: [patshaughnessy.net](https://patshaughnessy.net/2025/11/17/compiling-ruby-to-machine-language)
- Points: 1
- Comments: 1

## Topic Body

- **YJIT과 ZJIT**는 Ruby 3.x에서 루비 코드를 **기계어로 변환해 실행 속도를 높이는 JIT 컴파일러 구조**  
- YJIT은 각 **함수나 블록 호출 횟수를 카운트**해 일정 임계값에 도달하면 해당 코드를 기계어로 변환  
- 변환된 코드는 **YJIT 블록**에 저장되며, 각 블록은 여러 YARV 명령어를 대응하는 **ARM64 기계어 명령어**로 변환  
- **Branch Stub**을 사용해 런타임에 실제 데이터 타입을 관찰하고, 그에 맞는 기계어 명령어를 선택적으로 생성  
- 이러한 구조는 Ruby의 **실행 성능 향상과 동적 타입 처리 효율성**을 동시에 달성하기 위한 핵심 메커니즘  

---

### Chapter 4: 루비를 기계어로 컴파일하기

#### Interpreting vs. Compiling Ruby Code
- 원문에 세부 내용 없음  

#### Counting Method and Block Calls
- YJIT은 프로그램의 **함수 및 블록 호출 횟수**를 추적해 **핫스팟 코드**를 식별  
  - 각 함수나 블록의 YARV 명령어 시퀀스 옆에 **jit_entry**와 **jit_entry_calls** 값을 저장  
  - `jit_entry`는 초기에는 null이며, 나중에 YJIT이 생성한 기계어 코드의 포인터를 저장  
  - `jit_entry_calls`는 호출될 때마다 1씩 증가  
- 호출 횟수가 임계값에 도달하면 YJIT이 해당 코드를 **기계어로 컴파일**  
  - Ruby 3.5의 기본 임계값은 **작은 프로그램 30회**, **대규모 애플리케이션 120회**  
  - 실행 시 `--yjit-call-threshold` 옵션으로 변경 가능  
- 이 방식으로 YJIT은 자주 실행되는 코드만 기계어로 변환해 **효율적 실행 경로 확보**

#### YJIT Blocks
- YJIT은 생성한 기계어 명령어를 **YJIT 블록**에 저장  
  - YJIT 블록은 Ruby 블록과 다르며, **YARV 명령어의 일부 구간**을 대응  
  - 각 Ruby 함수나 블록은 여러 YJIT 블록으로 구성  
- 예시 프로그램에서 블록이 30번째 실행될 때 YJIT이 컴파일을 시작  
  - 첫 번째 YARV 명령어 `getlocal_WC_1`을 기계어로 변환해 새로운 YJIT 블록 생성  
  - 이후 `getlocal_WC_0` 명령어를 추가로 컴파일해 같은 블록에 포함  
- Figure 4-8에 따르면, YJIT은 **ARM64 명령어**를 생성해 M1 프로세서의 **x1, x9 레지스터**에 값을 로드  
  - `getlocal_WC_1`은 이전 스택 프레임의 지역 변수를, `getlocal_WC_0`은 현재 스택의 변수를 스택에 저장  
  - 생성된 기계어 명령어는 동일한 동작을 수행  

#### YJIT Branch Stubs
- YJIT이 `opt_plus` 명령어를 컴파일할 때 **피연산자 타입을 알 수 없는 문제** 발생  
  - 정수, 문자열, 부동소수점 등 타입에 따라 필요한 기계어 명령어가 다름  
  - 예: 정수 덧셈은 `adds` 명령어 사용, 부동소수점 덧셈은 다른 명령어 필요  
- 이를 해결하기 위해 YJIT은 **사전 분석 대신 런타임 관찰 방식**을 사용  
  - 프로그램 실행 중 실제 전달된 값의 타입을 확인해 그에 맞는 기계어를 생성  
- 이 동작을 위해 **Branch Stub**을 사용  
  - 새로운 분기(branch)가 아직 연결된 블록이 없을 때, 임시로 **stub**에 연결  
  - 이후 실제 타입이 확인되면 해당 stub을 적절한 블록으로 대체  

#### ZJIT (언급만 있음)
- 목차에 ZJIT 관련 섹션이 포함되어 있으나, 본문에 구체적 설명 없음  

---

### 요약
- YJIT은 Ruby 3.5에서 **동적 타입 언어의 실행 효율을 높이기 위한 JIT 컴파일러**  
- **호출 횟수 기반 컴파일 트리거**, **YJIT 블록 구조**, **Branch Stub을 통한 런타임 타입 확인**이 핵심  
- ARM64 아키텍처에서 실제 기계어 명령어로 변환해 **루비 코드의 실행 속도 향상**  
- ZJIT은 차세대 JIT으로 언급되지만, 세부 내용은 본문에 없음

## Comments



### Comment 46506

- Author: neo
- Created: 2025-11-19T04:33:08+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=45957629) 
- 예전에 **MacRuby**가 LLVM을 이용해 macOS에서 네이티브 코드로 컴파일되고, Objective‑C 프레임워크와 통합되던 시절이 있었음  
  꽤 멋진 아이디어였는데, 결국 Apple이 **Swift**로 방향을 바꾼 듯함  
  새 버전이 나오면 *Ruby Under a Microscope* 책을 꼭 사서 읽어볼 생각임. Ruby는 여전히 좋아하지만 실제로 쓸 기회가 많지 않았음
  - MacRuby의 제작자가 Apple을 떠난 뒤 **RubyMotion**을 만들었음  
    지금은 다른 사람들이 이어가고 있지만, 현재는 **DragonRuby**(게임 중심 Ruby 구현체)에 더 집중하는 분위기임
  - MacRuby는 저자가 떠난 후 [RubyMotion](http://www.rubymotion.com/)으로 이어졌음  
    참고로 [위키 문서](https://en.wikipedia.org/wiki/RubyMotion)도 있음
  - 지금도 Objective‑C를 써서 macOS, iOS, iPadOS용 앱을 만들 수 있음  
    다만 예전 API들은 더 이상 지원되지 않을 수도 있음
  - 여러 언어를 다뤄온 입장에서 보면, Apple이 Swift로 옮긴 건 마치 Microsoft가 **VB6에서 VB.Net**으로 넘어간 것과 비슷한 느낌임  
    VB6은 개발 속도가 정말 빨랐고, Direct3D나 ASP Classic까지 다룰 수 있었음  
    Ruby의 **우아함과 개발 편의성**이 그 시절을 떠올리게 함  
    만약 Ruby에 VB6 수준의 GUI 도구가 있었다면 인기도 꽤 달랐을 것 같음  

- Pat이 계속해서 프로젝트를 이어가는 걸 보니 정말 반가움  
  그의 첫 *Ruby Under a Microscope* 책과 블로그 글들은 내게 큰 **영감**을 줬음  
  예전에 Euruko 컨퍼런스에서 직접 만난 적도 있는데, 정말 훌륭한 사람이었음
  - 따뜻한 댓글에 감사함  

- 처음 *Ruby Under a Microscope*를 읽었을 때 정말 재미있었음  
  그 덕분에 예전에 **CTF 문제 풀이**에도 활용했음  
  요즘 Ruby 내부 구현을 따라가진 못했지만, 새 버전이 나오면 꼭 살 생각임
  - 2002년부터 2010년까지 Ruby를 많이 썼는데, 이후엔 거의 손을 놓았음  
    이번 글을 보고 새 버전 책을 다시 읽고 싶어졌음  

- Ruby 컴파일 얘기가 나와서 말인데, Stripe 개발자들이 만든 **Sorbet compiler**를 써본 적 있는지 궁금함  
  [Sorbet Compiler 공개 글](https://sorbet.org/blog/2021/07/30/open-sourcing-sorbet-compiler)
  - 지금은 저장소에서 사라졌고, 더 이상 개발되지 않는 듯해서 아쉬움  
    **AOT 컴파일**은 Ruby에선 정말 어려움  
    Sorbet의 접근이 흥미로운 이유는 Ruby의 **타입 검사**를 기반으로 빠른 경로를 만들 수 있기 때문임  
    나도 개인 프로젝트로 Ruby 컴파일러를 만들고 있는데, [hokstad.com/compiler](https://hokstad.com/compiler)와  
    [writing-a-compiler-in-ruby](https://github.com/vidarh/writing-a-compiler-in-ruby/)를 참고하고 있음  
    지금은 RubySpec 통과에 집중하고 있고, 나중엔 타입 기반 최적화도 시도해볼 생각임  

- Ruby 컴파일과는 직접 관련 없지만, *[Enterprise Integration with Ruby](https://www.goodreads.com/book/show/624316.Enterprise_Integration_with_Ruby)* 책이 웹 외의 영역에서 Ruby를 활용하는 데 큰 **통찰**을 줬음  

- **MRuby**를 알게 된 이후로, 내 프로젝트와 스크립트를 독립 실행 파일로 바꾸는 재미에 빠져 있음  

- *Ruby Under a Microscope*가 여전히 업데이트되고 있어서 기쁨  
  Ruby 내부 동작을 이해하려는 사람에게는 **필독서**라고 생각함  

- YJIT 블록이 여러 번 실행될 때, 입력 타입별로 어떻게 컴파일을 추적하는지 궁금했음  
  Ruby가 int나 float 등 다양한 타입을 어떻게 처리하는지 알고 싶음
  - 그게 바로 **YJIT의 핵심**임  
    실제 타입이 제공될 때까지 컴파일을 미루는 **“wait‑and‑see”** 접근을 사용함  
    각 타입별로 블록 버전을 따로 관리하고, 상황에 맞게 호출함  
    이 알고리즘은 **Basic Block Versioning**이라 불림  
    Shopify의 Maxime Chevalier‑Boisvert가 [RubyConf 2021 발표 영상](https://www.youtube.com/watch?v=zO9_uTaELCw)에서 잘 설명함  
    새 JIT 엔진인 ZJIT는 다른 방식을 쓰는 것으로 보임  

- 동적 타입 언어를 JIT으로 빠르게 만드는 건 보통 **메모리 사용량 증가**라는 대가를 치름  
  Shopify 같은 대형 기업이 아니라면 이게 더 큰 문제일 수 있음
  - 하지만 작은 기업은 보통 애플리케이션 규모도 작음  
    요즘 클라우드 인스턴스는 코어당 4GiB 정도 메모리를 주기 때문에, 수백 MB의 JIT 코드 정도는 충분히 감당 가능함  

- YJIT이 함수 호출 횟수만 세서 **핫스팟**을 찾는 방식이 단순해 보였음  
  JavaScript JIT처럼 루프 내부의 무거운 연산을 감지하는 기능은 없을까 궁금했음  
  Ruby의 블록 구조가 이런 최적화에 도움이 될 수도 있을 것 같음
  - 맞음, Ruby는 루프 본문을 블록으로 처리하기 때문에  
    JIT이 블록을 별도 함수처럼 다루면서 반복문을 자연스럽게 **최적화**할 수 있음  
    이 부분은 다음 장에서 더 깊이 다뤄볼 예정임
