JIT

Simple Example

The following example is a interpreter of brainfuck language with JIT. There is a detailed tutorial (part1, part2) on how to write a simple interpreter with RPython. Here, we briefly introduce some JIT-related data structures, functions, and decorators.

 1import os
 2from rpython.rlib.jit import JitDriver, elidable
 3
 4def get_location(pc, program, bracket_map):
 5    return "%s_%s_%s" % (program[:pc], program[pc], program[pc+1:])
 6
 7jitdriver = JitDriver(greens=['pc', 'program', 'bracket_map'],
 8                      reds=['tape'],
 9                      get_printable_location=get_location)
10
11@elidable
12def get_matching_bracket(bracket_map, pc):
13    return bracket_map[pc]
14
15def mainloop(program, bracket_map):
16    pc = 0
17    tape = Tape()
18
19    while pc < len(program):
20        jitdriver.jit_merge_point(pc=pc,
21                                  tape=tape,
22                                  program=program,
23                                  bracket_map=bracket_map)
24
25        code = program[pc]
26
27        if code == ">": tape.advance()
28        elif code == "<": tape.devance()
29        elif code == "+": tape.inc()
30        elif code == "-": tape.dec()
31        elif code == ".": os.write(1, chr(tape.get()))      # write
32        elif code == ",": tape.set(ord(os.read(0, 1)[0]))   # read from stdin
33        elif code == "[" and tape.get() == 0:
34            # Skip forward to the matching ]
35            pc = get_matching_bracket(bracket_map, pc)
36        elif code == "]" and tape.get() != 0:
37            # Skip back to the matching [
38            pc = get_matching_bracket(bracket_map, pc)
39
40        pc += 1
41
42class Tape(object):
43    def __init__(self):
44        self.thetape = [0]
45        self.position = 0
46
47    def get(self): return self.thetape[self.position]
48    def set(self, val): self.thetape[self.position] = val
49    def inc(self): self.thetape[self.position] += 1
50    def dec(self): self.thetape[self.position] -= 1
51    def advance(self):
52        self.position += 1
53        if len(self.thetape) <= self.position:
54            self.thetape.append(0)
55    def devance(self): self.position -= 1
56
57def parse(program):
58    parsed = []
59    bracket_map = {}
60    leftstack = []
61
62    pc = 0
63    for char in program:
64        if char in ('[', ']', '<', '>', '+', '-', ',', '.'):
65            parsed.append(char)
66
67            if char == '[':
68                leftstack.append(pc)
69            elif char == ']':
70                left = leftstack.pop()
71                right = pc
72                bracket_map[left] = right
73                bracket_map[right] = left
74            pc += 1
75
76    return "".join(parsed), bracket_map
77
78def run(filename):
79    with open(filename) as f:
80        program_contents = f.read()
81    program, bm = parse(program_contents)
82    mainloop(program, bm)
83
84def entry_point(argv):
85    try:
86        filename = argv[1]
87    except IndexError:
88        print "You must supply a filename"
89        return 1
90
91    run(filename)
92    return 0
93
94def target(*args): return entry_point, None

JitDriver

An interpreter wishing to use the RPython JIT generator must define a list of green variables and a list of red variables. The green variables are loop constants. They are used to identify the current loop. Red variables are for everything else used in the execution loop.

JIT Decorators and Functions

  • elidable

  • hint

  • promote

  • promote_string

  • dont_look_inside

  • look_inside

  • look_inside_iff

  • unroll_safe

  • loop_invariant

  • elidable_promote

  • oopspec

  • not_in_trace

  • isconstant

  • isvirtual

  • loop_unroll_heuristic

  • we_are_jitted

JIT Parameters

  • threshold: number of times a loop has to run for it to become hot

  • function_threshold: number of times a function must run for it to become traced from start

  • trace_eagerness: number of times a guard has to fail before we start compiling a bridge

  • decay: amount to regularly decay counters by (0=none, 1000=max)

  • trace_limit: number of recorded operations before we abort tracing with ABORT_TOO_LONG

  • inlining: inline python functions or not (1/0)

  • loop_longevity: a parameter controlling how long loops will be kept before being freed, an estimate

  • retrace_limit: how many times we can try retracing before giving up

  • max_retrace_guards: number of extra guards a retrace can cause

  • max_unroll_loops: number of extra unrollings a loop can cause

  • disable_unrolling: after how many operations we should not unroll

  • enable_opts: internal use only (may not work or lead to crashes): optimizations to enable, or all = intbounds:rewrite:virtualize:string:pure:earlyforce:heap:unroll

  • max_unroll_recursion: how many levels deep to unroll a recursive function

  • vec: turn on the vectorization optimization (vecopt). Supports x86 (SSE 4.1), powerpc (SVX), s390x SIMD

  • vec_cost: threshold for which traces to bail. Unpacking increases the counter, vector operation decrease the cost,

  • vec_all: try to vectorize trace loops that occur outside of the numpypy library

The default parameters are:

PARAMETERS = {'threshold': 1039, # just above 1024, prime
              'function_threshold': 1619, # slightly more than one above, also prime
              'trace_eagerness': 200,
              'decay': 40,
              'trace_limit': 6000,
              'inlining': 1,
              'loop_longevity': 1000,
              'retrace_limit': 0,
              'max_retrace_guards': 15,
              'max_unroll_loops': 0,
              'disable_unrolling': 200,
              'enable_opts': 'all',
              'max_unroll_recursion': 7,
              'vec': 0,
              'vec_all': 0,
              'vec_cost': 0,
              }