Share
Embed
Writing a basic x86-64 JIT compiler from scratch in stock Python
Shows how to JIT compile simple Python functions to native x86-64 machine code at runtime. Everything is done from scratch, using nothing but the built-in Python modules.
Christian Stigen Larsen
January 23, 2018
Transcript
196 slides
-
Writing a basic x86-64 JIT compiler from scratch in stock
Python Christian Stigen Larsen, 2018-01-23 https://csl.name -
from jitcompiler import * @jit def foo(a, b): return a*a
- b*b >>> foo(2, 3) -
from jitcompiler import * @jit def foo(a, b): return a*a
- b*b >>> foo(2, 3) --- JIT-compiling <function foo at 0x100c28c08> -
from jitcompiler import * @jit def foo(a, b): return a*a
- b*b >>> foo(2, 3) --- JIT-compiling <function foo at 0x100c28c08> -5 -
from jitcompiler import * @jit def foo(a, b): return a*a
- b*b >>> foo(2, 3) --- JIT-compiling <function foo at 0x100c28c08> -5 >>> foo(3, 4) -
from jitcompiler import * @jit def foo(a, b): return a*a
- b*b >>> foo(2, 3) --- JIT-compiling <function foo at 0x100c28c08> -5 >>> foo(3, 4) -7 -
>>> print(disassemble(foo)) 0x100b1d000 48 89 fb mov rbx, rdi 0x100b1d003
48 89 f8 mov rax, rdi 0x100b1d006 48 0f af c3 imul rax, rbx 0x100b1d00a 50 push rax 0x100b1d00b 48 89 f3 mov rbx, rsi 0x100b1d00e 48 89 f0 mov rax, rsi 0x100b1d011 48 0f af c3 imul rax, rbx 0x100b1d015 48 89 c3 mov rbx, rax 0x100b1d018 58 pop rax 0x100b1d019 48 29 d8 sub rax, rbx 0x100b1d01c c3 ret -
>>> print(disassemble(foo)) 0x100b1d000 48 89 fb mov rbx, rdi 0x100b1d003
48 89 f8 mov rax, rdi 0x100b1d006 48 0f af c3 imul rax, rbx 0x100b1d00a 50 push rax 0x100b1d00b 48 89 f3 mov rbx, rsi 0x100b1d00e 48 89 f0 mov rax, rsi 0x100b1d011 48 0f af c3 imul rax, rbx 0x100b1d015 48 89 c3 mov rbx, rax 0x100b1d018 58 pop rax 0x100b1d019 48 29 d8 sub rax, rbx 0x100b1d01c c3 ret -
Python Function Strategy Python Bound Name Bytecode Bytecode Bytecode IR
Optimized IR def foo -
Python Function Strategy Python Bound Name Bytecode Bytecode Bytecode IR
Optimized IR x86-64 Machine Code def foo -
Python Function Strategy Python Bound Name Memory Page Bytes …
Read + Write Bytecode Bytecode Bytecode IR Optimized IR x86-64 Machine Code def foo -
Python Function Strategy Python Bound Name Memory Page Bytes …
Read + Write Bytecode x86-64 Machine Code def foo -
Python Function Strategy Python Bound Name Memory Page Bytes …
Read + Write Bytecode x86-64 Machine Code Read + Execute def foo -
Strategy Python Bound Name Memory Page Bytes … Read +
Write x86-64 Machine Code Read + Execute FFI def foo -
Python Bytecode def foo(a, b): return a*a - b*b >>>
import dis >>> dis.dis(foo) -
Python Bytecode def foo(a, b): return a*a - b*b >>>
import dis >>> dis.dis(foo) 2 0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY 7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE -
Python Bytecode def foo(a, b): return a*a - b*b >>>
import dis >>> dis.dis(foo) 2 0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY 7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE -
Python Bytecode def foo(a, b): return a*a - b*b >>>
import dis >>> dis.dis(foo) 2 0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY 7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE -
Python Bytecode def foo(a, b): return a*a - b*b >>>
import dis >>> dis.dis(foo) 2 0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY 7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE -
Python Bytecode def foo(a, b): return a*a - b*b >>>
import dis >>> dis.dis(foo) 2 0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY 7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE -
Python Bytecode def foo(a, b): return a*a - b*b >>>
import dis >>> dis.dis(foo) 2 0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY 7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE -
Python Bytecode def foo(a, b): return a*a - b*b >>>
import dis >>> dis.dis(foo) 2 0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY 7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE -
Python Bytecode def foo(a, b): return a*a - b*b >>>
import dis >>> dis.dis(foo) 2 0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY 7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE -
0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY
7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE -
0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY
7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE -
0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY
7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE a -
0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY
7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE a a -
0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY
7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE a*a -
0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY
7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE a*a b -
0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY
7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE a*a b b -
0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY
7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE a*a b*b -
0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY
7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE a*a-b*b -
0 LOAD_FAST 0 (a) 3 LOAD_FAST 0 (a) 6 BINARY_MULTIPLY
7 LOAD_FAST 1 (b) 10 LOAD_FAST 1 (b) 13 BINARY_MULTIPLY 14 BINARY_SUBTRACT 15 RETURN_VALUE a*a-b*b -
0 LOAD_FAST 0 (a) >>> foo.__code__.co_code ‘|\x00\x00|\x00\x00\x14|\x01\x00|\x01\x00\x14\x185’ >>> bytecode =
map(ord, foo.__code__.co_code) -
0 LOAD_FAST 0 (a) >>> foo.__code__.co_code ‘|\x00\x00|\x00\x00\x14|\x01\x00|\x01\x00\x14\x185’ >>> bytecode =
map(ord, foo.__code__.co_code) >>> opcode = bytecode[0] -
0 LOAD_FAST 0 (a) >>> foo.__code__.co_code ‘|\x00\x00|\x00\x00\x14|\x01\x00|\x01\x00\x14\x185’ >>> bytecode =
map(ord, foo.__code__.co_code) >>> opcode = bytecode[0] >>> opcode 124 -
0 LOAD_FAST 0 (a) >>> foo.__code__.co_code ‘|\x00\x00|\x00\x00\x14|\x01\x00|\x01\x00\x14\x185’ >>> bytecode =
map(ord, foo.__code__.co_code) >>> opcode = bytecode[0] >>> opcode 124 >>> dis.opname[opcode] -
0 LOAD_FAST 0 (a) >>> foo.__code__.co_code ‘|\x00\x00|\x00\x00\x14|\x01\x00|\x01\x00\x14\x185’ >>> bytecode =
map(ord, foo.__code__.co_code) >>> opcode = bytecode[0] >>> opcode 124 >>> dis.opname[opcode] ‘LOAD_FAST’ -
0 LOAD_FAST 0 (a) >>> foo.__code__.co_code ‘|\x00\x00|\x00\x00\x14|\x01\x00|\x01\x00\x14\x185’ >>> bytecode =
map(ord, foo.__code__.co_code) >>> opcode = bytecode[0] >>> opcode 124 >>> dis.opname[opcode] ‘LOAD_FAST’ >>> arg = bytecode[1] | bytecode[2] << 8 -
0 LOAD_FAST 0 (a) >>> foo.__code__.co_code ‘|\x00\x00|\x00\x00\x14|\x01\x00|\x01\x00\x14\x185’ >>> bytecode =
map(ord, foo.__code__.co_code) >>> opcode = bytecode[0] >>> opcode 124 >>> dis.opname[opcode] ‘LOAD_FAST’ >>> arg = bytecode[1] | bytecode[2] << 8 >>> arg 0 -
0 LOAD_FAST 0 (a) >>> foo.__code__.co_code ‘|\x00\x00|\x00\x00\x14|\x01\x00|\x01\x00\x14\x185’ >>> bytecode =
map(ord, foo.__code__.co_code) >>> opcode = bytecode[0] >>> opcode 124 >>> dis.opname[opcode] ‘LOAD_FAST’ >>> arg = bytecode[1] | bytecode[2] << 8 >>> arg 0 >>> foo.__code__.co_varnames[arg] ‘a’ -
>>> dis.dis(bar) 1 0 LOAD_CONST 1 (123) 3 RETURN_VALUE >>>
def bar(): return 123 -
>>> dis.dis(bar) 1 0 LOAD_CONST 1 (123) 3 RETURN_VALUE >>>
def bar(): return 123 -
>>> dis.dis(bar) 1 0 LOAD_CONST 1 (123) 3 RETURN_VALUE >>>
def bar(): return 123 >>> bar.__code__.co_consts[1] 123 -
>>> dis.dis(bar) 1 0 LOAD_CONST 1 (123) 3 RETURN_VALUE >>>
def bar(): return 123 >>> bar.__code__.co_consts[1] 123 -
class Compiler(object): # … def fetch(self): byte = self.bytecode[self.index] self.index
+= 1 return byte def decode(self): opcode = self.fetch() opname = dis.opname[opcode] -
class Compiler(object): # … def fetch(self): byte = self.bytecode[self.index] self.index
+= 1 return byte def decode(self): opcode = self.fetch() opname = dis.opname[opcode] if takes_arg(opname): arg = self.fetch() | self.fetch() << 8 else: arg = None -
class Compiler(object): # … def fetch(self): byte = self.bytecode[self.index] self.index
+= 1 return byte def decode(self): opcode = self.fetch() opname = dis.opname[opcode] if takes_arg(opname): arg = self.fetch() | self.fetch() << 8 else: arg = None return opname, arg -
def compile(self): while self.index < len(self.bytecode): op, arg = self.decode()
if op == “LOAD_FAST”: yield “push”, self.variable(arg), None elif … -
yield “push”, self.variable(arg), None IR & Assembly rax rbx rcx
… rsi rdi … Registers return address Stack -
push yield “push”, self.variable(arg), None IR & Assembly rax rbx
rcx … rsi rdi … Registers return address Stack -
push yield “push”, self.variable(arg), None IR & Assembly rax rbx
rcx … rsi rdi … Registers return address Stack -
push yield “push”, self.variable(arg), None IR & Assembly rax rbx
rcx … rsi rdi … Registers return address Stack -
push yield “push”, self.variable(arg), None IR & Assembly rax rbx
rcx … rsi rdi … Registers return address Stack -
push yield “push”, self.variable(arg), None IR & Assembly rax rbx
rcx … rsi rdi … Registers return address Stack def variable(self, index): passing_order = (“rdi”, “rsi”, “rdx”, “rcx”) return passing_order[index] -
push yield “push”, self.variable(arg), None IR & Assembly rax rbx
rcx … rsi rdi … Registers return address Stack -
push yield “push”, self.variable(arg), None IR & Assembly rax rbx
rcx … rsi rdi … Registers return address Stack rdi -
def baz(a, b): a = b LOAD_FAST 1 (b) STORE_FAST
0 (a) LOAD_CONST 0 (None) RETURN_VALUE -
LOAD_FAST 1 def baz(a, b): a = b LOAD_FAST 1
(b) STORE_FAST 0 (a) LOAD_CONST 0 (None) RETURN_VALUE -
LOAD_FAST 1 push rsi def baz(a, b): a = b
LOAD_FAST 1 (b) STORE_FAST 0 (a) LOAD_CONST 0 (None) RETURN_VALUE -
LOAD_FAST 1 push rsi STORE_FAST 0 def baz(a, b): a
= b LOAD_FAST 1 (b) STORE_FAST 0 (a) LOAD_CONST 0 (None) RETURN_VALUE -
LOAD_FAST 1 push rsi STORE_FAST 0 pop rax mov rdi,
rax def baz(a, b): a = b LOAD_FAST 1 (b) STORE_FAST 0 (a) LOAD_CONST 0 (None) RETURN_VALUE -
LOAD_FAST 1 push rsi STORE_FAST 0 pop rax mov rdi,
rax def baz(a, b): a = b LOAD_FAST 1 (b) STORE_FAST 0 (a) LOAD_CONST 0 (None) RETURN_VALUE LOAD_CONST 0 -
LOAD_FAST 1 push rsi STORE_FAST 0 pop rax mov rdi,
rax def baz(a, b): a = b LOAD_FAST 1 (b) STORE_FAST 0 (a) LOAD_CONST 0 (None) RETURN_VALUE LOAD_CONST 0 imm rax, 0 push rax -
LOAD_FAST 1 push rsi STORE_FAST 0 pop rax mov rdi,
rax def baz(a, b): a = b LOAD_FAST 1 (b) STORE_FAST 0 (a) LOAD_CONST 0 (None) RETURN_VALUE LOAD_CONST 0 imm rax, 0 push rax RETURN_VALUE -
LOAD_FAST 1 push rsi STORE_FAST 0 pop rax mov rdi,
rax def baz(a, b): a = b LOAD_FAST 1 (b) STORE_FAST 0 (a) LOAD_CONST 0 (None) RETURN_VALUE LOAD_CONST 0 imm rax, 0 push rax RETURN_VALUE pop rax ret -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax rsi rdi return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax rsi 22 rdi 11 return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax rsi 22 rdi 11 return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax rsi 22 rdi 11 return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax rsi 22 rdi 11 return address 22 -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax rsi 22 rdi 11 return address 22 -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 22 rsi 22 rdi 11 return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 22 rsi 22 rdi 11 return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 22 rsi 22 rdi 22 return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 22 rsi 22 rdi 22 return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 return address 0 -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 return address 0 -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 return address -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 LOAD_FAST push <reg> STORE_FAST pop rax mov <reg>, rax LOAD_CONST imm rax push rax BINARY_MULTIPLY pop rax pop rbx imul rax, rbx push rax BINARY_ADD / INPLACE_ADD pop rax pop rbx add rax, rbx push rax BINARY_SUBTRACT / INPLACE_SUBTRACT pop rbx pop rax sub rax, rbx push rax UNARY_NEGATIVE pop rax neg rax push rax RETURN_VALUE pop rax ret -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 LOAD_FAST push <reg> STORE_FAST pop rax mov <reg>, rax LOAD_CONST imm rax push rax BINARY_MULTIPLY pop rax pop rbx imul rax, rbx push rax BINARY_ADD / INPLACE_ADD pop rax pop rbx add rax, rbx push rax BINARY_SUBTRACT / INPLACE_SUBTRACT pop rbx pop rax sub rax, rbx push rax UNARY_NEGATIVE pop rax neg rax push rax RETURN_VALUE pop rax ret -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 LOAD_FAST push <reg> STORE_FAST pop rax mov <reg>, rax LOAD_CONST imm rax push rax BINARY_MULTIPLY pop rax pop rbx imul rax, rbx push rax BINARY_ADD / INPLACE_ADD pop rax pop rbx add rax, rbx push rax BINARY_SUBTRACT / INPLACE_SUBTRACT pop rbx pop rax sub rax, rbx push rax UNARY_NEGATIVE pop rax neg rax push rax RETURN_VALUE pop rax ret -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 LOAD_FAST push <reg> STORE_FAST pop rax mov <reg>, rax LOAD_CONST imm rax push rax BINARY_MULTIPLY pop rax pop rbx imul rax, rbx push rax BINARY_ADD / INPLACE_ADD pop rax pop rbx add rax, rbx push rax BINARY_SUBTRACT / INPLACE_SUBTRACT pop rbx pop rax sub rax, rbx push rax UNARY_NEGATIVE pop rax neg rax push rax RETURN_VALUE pop rax ret -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 LOAD_FAST push <reg> STORE_FAST pop rax mov <reg>, rax LOAD_CONST imm rax push rax BINARY_MULTIPLY pop rax pop rbx imul rax, rbx push rax BINARY_ADD / INPLACE_ADD pop rax pop rbx add rax, rbx push rax BINARY_SUBTRACT / INPLACE_SUBTRACT pop rbx pop rax sub rax, rbx push rax UNARY_NEGATIVE pop rax neg rax push rax RETURN_VALUE pop rax ret -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 LOAD_FAST push <reg> STORE_FAST pop rax mov <reg>, rax LOAD_CONST imm rax push rax BINARY_MULTIPLY pop rax pop rbx imul rax, rbx push rax BINARY_ADD / INPLACE_ADD pop rax pop rbx add rax, rbx push rax BINARY_SUBTRACT / INPLACE_SUBTRACT pop rbx pop rax sub rax, rbx push rax UNARY_NEGATIVE pop rax neg rax push rax RETURN_VALUE pop rax ret -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 LOAD_FAST push <reg> STORE_FAST pop rax mov <reg>, rax LOAD_CONST imm rax push rax BINARY_MULTIPLY pop rax pop rbx imul rax, rbx push rax BINARY_ADD / INPLACE_ADD pop rax pop rbx add rax, rbx push rax BINARY_SUBTRACT / INPLACE_SUBTRACT pop rbx pop rax sub rax, rbx push rax UNARY_NEGATIVE pop rax neg rax push rax RETURN_VALUE pop rax ret -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 LOAD_FAST push <reg> STORE_FAST pop rax mov <reg>, rax LOAD_CONST imm rax push rax BINARY_MULTIPLY pop rax pop rbx imul rax, rbx push rax BINARY_ADD / INPLACE_ADD pop rax pop rbx add rax, rbx push rax BINARY_SUBTRACT / INPLACE_SUBTRACT pop rbx pop rax sub rax, rbx push rax UNARY_NEGATIVE pop rax neg rax push rax RETURN_VALUE pop rax ret -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 LOAD_FAST push <reg> STORE_FAST pop rax mov <reg>, rax LOAD_CONST imm rax push rax BINARY_MULTIPLY pop rax pop rbx imul rax, rbx push rax BINARY_ADD / INPLACE_ADD pop rax pop rbx add rax, rbx push rax BINARY_SUBTRACT / INPLACE_SUBTRACT pop rbx pop rax sub rax, rbx push rax UNARY_NEGATIVE pop rax neg rax push rax RETURN_VALUE pop rax ret -
push rsi pop rax mov rdi, rax def baz(a, b):
a = b imm rax, 0 push rax pop rax ret >>> baz(11, 22) rax 0 rsi 22 rdi 22 -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret mov rax, rsi -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret mov rax, rsi -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret mov rax, rsi Peephole Optimization -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret mov rax, rsi Peephole Optimization -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret mov rax, rsi mov rdi, rax Peephole Optimization -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret mov rax, rsi mov rdi, rax Peephole Optimization -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret mov rax, rsi mov rdi, rax imm rax, 0 Peephole Optimization -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret mov rax, rsi mov rdi, rax imm rax, 0 Peephole Optimization -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret mov rax, rsi mov rdi, rax imm rax, 0 Peephole Optimization -
push rsi pop rax mov rdi, rax imm rax, 0
push rax pop rax ret mov rax, rsi mov rdi, rax imm rax, 0 ret Peephole Optimization -
mov rax, rsi mov rdi, rax imm rax, 0 ret
Peephole Optimization -
class Assembler(object): # … def emit(self, *args): for code in
args: self.block[self.index] = code self.index += 1 Machine Code Generation -
class Assembler(object): # … def emit(self, *args): for code in
args: self.block[self.index] = code self.index += 1 Machine Code Generation def push(self, a, dummy): self.emit(0x50 | self.registers(a)) -
class Assembler(object): # … def emit(self, *args): for code in
args: self.block[self.index] = code self.index += 1 Machine Code Generation def push(self, a, dummy): self.emit(0x50 | self.registers(a)) -
class Assembler(object): # … def emit(self, *args): for code in
args: self.block[self.index] = code self.index += 1 Machine Code Generation def push(self, a, dummy): self.emit(0x50 | self.registers(a)) def registers(self, a): order = (“rax”, “rcx”, …) return order.index(a) -
class Assembler(object): # … def emit(self, *args): for code in
args: self.block[self.index] = code self.index += 1 Machine Code Generation def push(self, a, dummy): self.emit(0x50 | self.registers(a)) def registers(self, a): order = (“rax”, “rcx”, …) return order.index(a) -
def add(self, a, b): self.emit(0x48, 0x01, 0xc0 | self.registers(a, b))
def immediate(self, a, constant): self.emit(0x48, 0xb8 | self.registers(a), *self.little_endian(constant)) Machine Code Generation -
def add(self, a, b): self.emit(0x48, 0x01, 0xc0 | self.registers(a, b))
def immediate(self, a, constant): self.emit(0x48, 0xb8 | self.registers(a), *self.little_endian(constant)) Machine Code Generation -
def add(self, a, b): self.emit(0x48, 0x01, 0xc0 | self.registers(a, b))
def immediate(self, a, constant): self.emit(0x48, 0xb8 | self.registers(a), *self.little_endian(constant)) Machine Code Generation -
def add(self, a, b): self.emit(0x48, 0x01, 0xc0 | self.registers(a, b))
def immediate(self, a, constant): self.emit(0x48, 0xb8 | self.registers(a), *self.little_endian(constant)) Machine Code Generation -
def add(self, a, b): self.emit(0x48, 0x01, 0xc0 | self.registers(a, b))
def immediate(self, a, constant): self.emit(0x48, 0xb8 | self.registers(a), *self.little_endian(constant)) Machine Code Generation -
def ret(self, a, b): self.emit(0xc3) def push(self, a, _): self.emit(0x50
| self.registers(a)) def pop(self, a, _): self.emit(0x58 | self.registers(a)) def imul(self, a, b): self.emit(0x48, 0x0f, 0xaf, 0xc0 | self.registers(a, b)) def add(self, a, b): self.emit(0x48, 0x01, 0xc0 | self.registers(b, a)) def sub(self, a, b): self.emit(0x48, 0x29, 0xc0 | self.registers(b, a)) def neg(self, a, _): self.emit(0x48, 0xf7, 0xd8 | self.register(a)) def mov(self, a, b): self.emit(0x48, 0x89, 0xc0 | self.registers(b, a)) def immediate(self, a, number): self.emit(0x48, 0xb8 | self.registers(a), *self.little_endian(number)) Machine Code Generation -
Memory Management • Allocate one page of memory using mmap
• Use mprotect to change from R+W to R+X -
Memory Management def create_block(size): ptr = mmap(0, size, MMAP.PROT_WRIT |
MMAP.PROT_READ | MMAP.MAP_ANONYMOUS, 0, 0) if ptr == MAP_FAILED: raise RuntimeError(…) return ptr -
Memory Management def create_block(size): ptr = mmap(0, size, MMAP.PROT_WRIT |
MMAP.PROT_READ | MMAP.MAP_ANONYMOUS, 0, 0) if ptr == MAP_FAILED: raise RuntimeError(…) return ptr -
Memory Management def create_block(size): ptr = mmap(0, size, MMAP.PROT_WRIT |
MMAP.PROT_READ | MMAP.MAP_ANONYMOUS, 0, 0) if ptr == MAP_FAILED: raise RuntimeError(…) return ptr -
Memory Management def create_block(size): ptr = mmap(0, size, MMAP.PROT_WRIT |
MMAP.PROT_READ | MMAP.MAP_ANONYMOUS, 0, 0) if ptr == MAP_FAILED: raise RuntimeError(…) return ptr -
Memory Management def make_executable(block, size): if mprotect(block, size, MMAP.PROT_READ |
MMAP.PROT_EXEC) != 0: raise RuntimeError(…) -
Memory Management def make_executable(block, size): if mprotect(block, size, MMAP.PROT_READ |
MMAP.PROT_EXEC) != 0: raise RuntimeError(…) -
FFI • Use ctypes as FFI • Grab machine code
address, create signature and bind to function. -
FFI • Use ctypes as FFI • Grab machine code
address, create signature and bind to function. • Ctypes also seem to save and restore registers -
FFI • Use ctypes as FFI • Grab machine code
address, create signature and bind to function. • Ctypes also seem to save and restore registers • More overhead: Marshalling arguments -
FFI signature = ctypes.CFUNCTYPE([ctypes.c_int64, ctypes.c_int64]) signature.restype = ctypes.c_int64 func =
signature(assembler.address) print(func(1, 2)) -
FFI signature = ctypes.CFUNCTYPE([ctypes.c_int64, ctypes.c_int64]) signature.restype = ctypes.c_int64 func =
signature(assembler.address) print(func(1, 2)) -
FFI signature = ctypes.CFUNCTYPE([ctypes.c_int64, ctypes.c_int64]) signature.restype = ctypes.c_int64 func =
signature(assembler.address) print(func(1, 2)) -
FFI signature = ctypes.CFUNCTYPE([ctypes.c_int64, ctypes.c_int64]) signature.restype = ctypes.c_int64 func =
signature(assembler.address) print(func(1, 2)) -
FFI signature = ctypes.CFUNCTYPE([ctypes.c_int64, ctypes.c_int64]) signature.restype = ctypes.c_int64 func =
signature(assembler.address) print(func(1, 2)) -
The “@jit” Decorator def jit(func): def front(*args, **kw): if not
hasattr(front, “func”): front.func = compile_native(func) return front.func(*args, **kw) return front @jit def foo(a, b): return a*a - b*b -
The “@jit” Decorator def jit(func): def front(*args, **kw): if not
hasattr(front, “func”): front.func = compile_native(func) return front.func(*args, **kw) return front @jit def foo(a, b): return a*a - b*b -
Things we haven’t touched on • Code generation: SSA, branching
• Optimization: TAC, register allocation -
Things we haven’t touched on • Code generation: SSA, branching
• Optimization: TAC, register allocation • Compatibility: Python object system -
Things we haven’t touched on • Code generation: SSA, branching
• Optimization: TAC, register allocation • Compatibility: Python object system • Performance gotchas -
Pointers • Full details on blog post at https://csl.name/post/python-compiler/ •
PeachPy • Truffle + Graal, LLVM, libjit, etc. • NUMBA