Master the fundamental concepts of jit compilation through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksSelf-hosting JITs compile their own optimizing tiers to native code. LuaJIT's FFI and HotSpot's C2 eventually run as generated machine code. The bootstrap compiles a minimal compiler, then uses it to compile the full compiler for speed.
Stage 0: C interpreter. Stage 1: minimal JIT enough to compile stage 2. Stage 2: full optimizer emits code for itself and user programs.
cLoading…
Production compilers embed this step inside a longer pipeline. GCC flows through cpp, cc1, assembly, and ld; Clang uses the driver, Sema, LLVM IR passes, and a target backend. LLVM bitcode, JVM bytecode, and WASM are other familiar IRs at the same layer. The exercise isolates one pass so you can test it alone before chaining it to the next stage.
You will implement a self-hosting JIT that compiles its own compiler logic. This exercise requires bootstrapping from a minimal emitter to a JIT that generates native code for compiler functions themselves.
Build the heart of a self-extending language system, the idea behind self-hosting compilers, in its most compact classic form: a Forth whose compiler is available while programs run. Every : name ... ; definition is compiled on the spot into threaded code, becomes a new word, and can immediately be used to compile further words. The language grows out of itself.
Forth source: whitespace-separated tokens over any number of lines.
Interpreting (outside a definition), each token is executed immediately:
| Token | Effect (stack effect: before -- after) |
|---|---|
integer (42, -7) | push it |
+ - * / | ( a b -- a⊕b ); / truncates toward zero |
= < | ( a b -- flag ), flag is 1 or 0 |
dup drop swap over | ( a -- a a ), ( a -- ), ( a b -- b a ), ( a b -- a b a ) |
. | pop and print the number followed by one space |
cr | print a newline |
: NAME ... ; | compile a new word |
see NAME | print the word's compiled code (starting on a fresh line) |
| a defined word | run it |
Compiling (inside : … ;), tokens become cells:
| Token | Cell(s) |
|---|---|
| primitive | PRIM (shown by its name) |
| integer | lit N |
| defined word | call NAME: bound to the latest definition at compile time |
if | ?branch T (jump to cell T if the popped value is 0): T filled in later |
else | branch T; the pending if now jumps just past this cell |
then | resolves the pending if/else to the next cell |
begin … until | until compiles ?branch B, jumping back to the cell index where begin was |
; | exit; the word becomes visible |
Whatever the program prints; then stack: v v ... (bottom to top, or stack: empty) and words: N (number of definitions made, including redefinitions). see prints NAME: [0] CELL [1] CELL ....
Errors (unknown word X, stack underflow, division by zero) print error: MESSAGE on a fresh line and stop reading input; the final two lines are still printed.
Input:
cLoading…
Output:
cLoading…
call recursing into another word.if/else/then and begin/until back-patching.Hidden tests cover recurse, nested if/else, redefinition (earlier words keep the old binding), and an unknown word.