Oseo: SBCL 스타일의 JavaScript/TypeScript AOT 컴파일러

유형
발표
트랙
개발 언어 및 툴체인 (Programming Languages & Tooling)
언어
한국어

Common Lisp의 SBCL이나 Scheme의 Chez Scheme은 동적 타입 언어이면서도 프로그램 전체를 미리 네이티브 코드로 컴파일해 수십 년째 프로덕션에서 쓰이고 있습니다. 반면 JavaScript는 여전히 인터프리터와 여러 단계의 JIT, 그 위에 얹힌 디옵티마이제이션 메커니즘 없이는 빠르게 돌아가지 못하는 언어로 여겨집니다. 왜 그럴까요? 이 질문에서 출발한 프로젝트가 Oseo입니다.

Oseo는 모든 함수를 두 겹으로 컴파일합니다. 언어 전체 의미를 구현하는 제네릭 경로가 하나 있고, 값의 종류나 형태에 대해 저렴한 런타임 가드를 걸 수 있는 곳마다 그 옆에 특수화된 경로를 나란히 둡니다. 가드가 실패하면 이미 바이너리 안에 컴파일되어 있는 제네릭 경로로 분기할 뿐입니다. 인터프리터로 떨어지지도, 디옵티마이즈하지도 않습니다. TypeScript의 타입 표기와 JSDoc은 이 특수화 경로를 선택하는 힌트로 쓰이지만, 결코 덮어놓고 신뢰하는 것은 아닙니다. any나 타입 단언, 순수 JavaScript와의 상호운용 때문에 : number라는 표기는 언제든 틀릴 수 있고, Oseo는 그 가능성을 가드로 방어합니다.

이 발표에서는 이 아이디어가 실제로 어떻게 동작하는지를 코드와 바이너리로 직접 보여드리려 합니다. Node.js나 Deno 없이, V8이 전혀 링크되지 않은 순수 네이티브 실행 파일이 어떻게 만들어지는지, --dump-mir와 --emit-c로 들여다본 생성 코드에서 가드 하나가 실제로 어떤 비교와 분기로 컴파일되는지를 보여드립니다. 타입 힌트가 실행 결과를 바꿔서는 안 된다는 원칙도 다른 프로젝트와의 비교를 통해 구체적인 예제로 함께 살펴봅니다.

Oseo는 아직 완성된 프로젝트가 아닙니다. 이 발표는 지금까지 증명된 것과 아직 남은 길을 함께 보여드리는 자리이기도 합니다. 네이티브 I/O, 가비지 컬렉터, 셀프 호스팅 부트스트랩까지, 시스템 프로그래밍에 관심 있는 분이라면 기여할 여지가 많은 지점들을 짚어보려 합니다.


SBCL for Common Lisp and Chez Scheme for Scheme are dynamically typed, yet both compile whole programs to native code ahead of time and have run in production for decades. JavaScript, meanwhile, is still assumed to need an interpreter, several tiers of JIT, and a deoptimization mechanism on top just to run fast. Why the difference? That question is where Oseo started.

Oseo compiles every function twice over. A generic path implements the full semantics of the language; wherever a value’s kind or shape allows a cheap runtime guard, a specialized path sits beside it. When a guard fails, control simply branches to the generic path already compiled into the binary. Nothing falls back to an interpreter, nothing deoptimizes. TypeScript type annotations and JSDoc comments guide which specialized path gets picked, but they are never taken on faith: any, type assertions, and interop with untyped JavaScript mean a : number annotation can be wrong, and Oseo guards against that possibility rather than trusting it.

This talk shows the idea working, in code and in binaries. I’ll walk through how a pure native executable gets built without Node.js or Deno, with no V8 linked in at all, and look at what a guard actually compiles down to by reading the generated code through --dump-mir and --emit-c. I’ll also demonstrate, with a concrete comparison against another project in the same space, why a type hint must never be allowed to change what a program actually does.

Oseo is not finished. This talk is as much about what remains as what’s already proven: native I/O, garbage collection, self-hosting. Anyone with an interest in systems programming will find plenty of open ground to contribute to.

발표자