Created
November 26, 2025 18:07
-
-
Save jdmichaud/481698c3da0691cec3ef7a9bccfe3231 to your computer and use it in GitHub Desktop.
jit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <sys/mman.h> | |
| #include <string.h> | |
| #include <stdio.h> | |
| int main() { | |
| /* | |
| This is raw x86-64 machine code for a TINY *leaf function*: | |
| lea eax, [rdi + 1] ; compute (arg + 1) | |
| ret ; return | |
| Why this works without stack manipulation: | |
| - According to the Linux x86-64 System V ABI: | |
| • The first integer argument is passed in RDI | |
| • The return value must be placed in EAX | |
| • Leaf functions do NOT need a stack frame | |
| as long as they: | |
| - don’t touch the stack | |
| - don’t use local variables | |
| - don’t call other functions | |
| - don’t modify callee-saved registers | |
| (RBX, RBP, R12–R15) | |
| • The *caller* is responsible for RSP alignment | |
| before calling the function | |
| Because our function: | |
| - uses only RDI → EAX | |
| - does not use the stack | |
| - does not touch callee-saved registers | |
| - returns immediately | |
| …it is a completely valid function without: | |
| push rbp | |
| mov rbp, rsp | |
| sub rsp, XXX | |
| (i.e. the usual prologue/epilogue) | |
| This is why JIT engines often generate tiny stackless stubs. | |
| */ | |
| unsigned char code[] = { | |
| 0x8D, 0x47, 0x01, // lea eax, [rdi + 1] | |
| 0xC3 // ret | |
| }; | |
| /* | |
| JIT STEP 1: Allocate executable memory | |
| We ask Linux for a memory region that is: | |
| - writable (so we can place instructions) | |
| - executable (so the CPU can run them) | |
| In real JITs, this is usually done in two steps | |
| (W^X security): first WRITE, then mprotect() to EXEC. | |
| For simplicity, we request both at once. | |
| */ | |
| void* mem = mmap(NULL, sizeof(code), | |
| PROT_WRITE | PROT_EXEC, | |
| MAP_PRIVATE | MAP_ANONYMOUS, | |
| -1, 0); | |
| /* | |
| JIT STEP 2: Copy our generated machine code into that memory. | |
| After this, the buffer contains valid executable instructions. | |
| */ | |
| memcpy(mem, code, sizeof(code)); | |
| /* | |
| JIT STEP 3: Treat the memory region as a function. | |
| We cast the pointer to a function pointer type whose | |
| signature matches the code we wrote: | |
| int func(int) | |
| The ABI ensures: | |
| - Argument arrives in RDI | |
| - Return value in EAX | |
| - Caller has aligned the stack | |
| */ | |
| int (*func)(int) = mem; | |
| /* | |
| JIT STEP 4: Call it like a normal function. | |
| The CPU jumps into our generated code and executes it. | |
| */ | |
| printf("%d\n", func(41)); // prints 42 | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment