├── README └── simple_tracing_jit.py /README: -------------------------------------------------------------------------------- 1 | *This is a work in progress* 2 | 3 | The purpose of this project is to build a simple, easy to understand interpreter with a tracing JIT. Tracing is a novel technique for improving the performance of interpreters and VMs described by Andreas Gal ( http://www.ics.uci.edu/~franz/Site/pubs-pdf/ICS-TR-07-12.pdf ). This technique has been implemented in the Python interpreter PyPy ( http://codespeak.net/pypy/extradoc/talk/icooolps2009/bolz-tracing-jit-final.pdf ) and Mozilla Firefox ( http://uci.academia.edu/AndreasGal/Papers/212381/Trace-Based_Just-In-Time_Type_Specialization_for_Dynamic_Languages ) with very good results. 4 | 5 | 6 | -------------------------------------------------------------------------------- /simple_tracing_jit.py: -------------------------------------------------------------------------------- 1 | PUSH, ADD, JUMP, GT, HALT, POP = range(6) 2 | 3 | one_simple_loop = [ 4 | PUSH, 0, # 0 5 | GT, 50000, 9, # 2 6 | ADD, 1, # 5 7 | JUMP, 2, # 7 8 | HALT # 9 9 | ] 10 | 11 | two_simple_loops = [ 12 | PUSH, 0, # 0 13 | GT, 50000, 9, # 2 14 | ADD, 1, # 5 15 | JUMP, 2, # 7 16 | 17 | GT, 100000, 16, # 9 18 | ADD, 2, # 12 19 | JUMP, 9, # 14 20 | 21 | HALT # 16 22 | ] 23 | 24 | nested_loops = [ 25 | PUSH, 0, # 0 - outer loop counter 26 | GT, 30, 19, # 2 - start of outer loop 27 | PUSH, 0, # 5 - inner loop counter 28 | GT, 15, 14, # 7 - start of inner loop 29 | ADD, 1, # 10 30 | JUMP, 7, # 12 - end of inner loop 31 | POP, # 14 32 | ADD, 2, # 15 33 | JUMP, 2, # 17 - end of outer loop 34 | 35 | HALT # 19 36 | ] 37 | 38 | #code = one_simple_loop 39 | #code = two_simple_loops 40 | #code = nested_loops 41 | 42 | class Interpreter(object): 43 | def __init__(self, pc, stack, code): 44 | self.pc = pc 45 | self.stack = stack 46 | self.code = code 47 | 48 | def run_PUSH(self): 49 | #print "Running PUSH" 50 | self.stack.append(code[self.pc+1]) 51 | self.pc += 2 52 | 53 | def run_GT(self): 54 | #print "Running GT" 55 | if self.stack[-1] > code[self.pc+1]: 56 | self.pc = code[self.pc+2] 57 | else: 58 | self.pc += 3 59 | 60 | def run_ADD(self): 61 | #print "Running ADD" 62 | self.stack[-1] += code[self.pc+1] 63 | self.pc += 2 64 | 65 | def run_JUMP(self): 66 | #print "Running JUMP" 67 | self.pc = code[self.pc+1] 68 | 69 | def run_POP(self): 70 | #print "Running POP" 71 | self.stack.pop() 72 | self.pc += 1 73 | 74 | def interpret(self): 75 | while True: 76 | instruction_to_run = self.code[self.pc] 77 | 78 | if instruction_to_run == PUSH: 79 | self.run_PUSH() 80 | elif instruction_to_run == GT: 81 | self.run_GT() 82 | elif instruction_to_run == ADD: 83 | self.run_ADD() 84 | elif instruction_to_run == JUMP: 85 | self.run_JUMP() 86 | elif instruction_to_run == POP: 87 | self.run_POP() 88 | elif instruction_to_run == HALT: 89 | return self.stack[-1] 90 | 91 | class UnknownTraceRecordError(Exception): 92 | pass 93 | 94 | class TracingInterpreter(Interpreter): 95 | def __init__(self, pc, stack, code, loops, recording_trace): 96 | self.loops = loops 97 | self.recording_trace = recording_trace 98 | self.jitted_code_scope = {'GuardFailed': GuardFailed, 'self': self} 99 | self.trace_id = 0 100 | 101 | Interpreter.__init__(self, pc, stack, code) 102 | 103 | def translate_trace(self, loop_info): 104 | trace = loop_info['trace'] 105 | # create python code to run the trace 106 | executable_trace = ''' 107 | def trace_%d(): 108 | while True:''' % loop_info['trace_id'] 109 | 110 | for trace_step in trace: 111 | if trace_step[0] == TRACE_INSTR: 112 | if trace_step[1] == JUMP: 113 | compiled_code = ''' 114 | self.pc = %d''' % (trace_step[2]) 115 | elif trace_step[1] == ADD: 116 | compiled_code = ''' 117 | self.stack[-1] += %d 118 | self.pc += 2''' % (trace_step[2]) 119 | elif trace_step[1] == PUSH: 120 | compiled_code = ''' 121 | self.stack.append(%d) 122 | self.pc += 2''' % (trace_step[2]) 123 | elif trace_step[1] == POP: 124 | compiled_code = ''' 125 | self.stack.pop() 126 | self.pc += 1''' 127 | 128 | 129 | elif trace_step[0] == TRACE_GUARD_GT_JUMP: 130 | compiled_code = ''' 131 | if self.stack[-1] <= %d: 132 | raise GuardFailed()''' % (trace_step[1]) 133 | 134 | elif trace_step[0] == TRACE_GUARD_GT_NOT_JUMP: 135 | compiled_code = ''' 136 | if self.stack[-1] > %d: 137 | raise GuardFailed()''' % (trace_step[1]) 138 | elif trace_step[0] == TRACE_ENTER_TRACE: 139 | compiled_code = ''' 140 | trace_%d()''' % (trace_step[1]['trace_id']) 141 | else: 142 | raise UnknownTraceRecordError() 143 | 144 | executable_trace += compiled_code 145 | 146 | return executable_trace 147 | 148 | def print_state(self): 149 | print "State is pc =", self.pc, "stack =", self.stack 150 | #print "JITted scope =", self.jitted_code_scope 151 | 152 | def enter_trace(self, loop_info): 153 | #print loop_info['executable_trace'] 154 | exec loop_info['executable_trace'] in self.jitted_code_scope # defines the trace in the jitted context 155 | exec 'trace_%d()' % (loop_info['trace_id']) in self.jitted_code_scope 156 | 157 | def run_JUMP(self): 158 | old_pc = self.pc 159 | new_pc = self.code[self.pc+1] 160 | 161 | if new_pc < old_pc: 162 | if (new_pc, old_pc) in self.loops: 163 | loop_info = self.loops[(new_pc, old_pc)] 164 | 165 | loop_info['hotness'] += 1 166 | 167 | if loop_info['has_trace']: 168 | Interpreter.run_JUMP(self) # run the jump, then run the trace 169 | #print "Running previously-compiled trace for loop", new_pc, "-", old_pc 170 | print "Starting trace", new_pc, '-', old_pc 171 | self.print_state() 172 | try: 173 | self.enter_trace(loop_info) 174 | # can a trace leave normally? no, it is an infinite loop 175 | except GuardFailed: 176 | print "Guard failed, leaving trace for interpreter execution" 177 | self.print_state() 178 | return # Trace execution was not good for this iteration, so, fallback to regular interpreter 179 | # the jitted code is modifying interpreter state, no need to sync 180 | 181 | if loop_info['hotness'] > 10 and loop_info['has_trace'] == False: 182 | if not self.recording_trace: 183 | print "Found new hot loop from", new_pc, "to", old_pc, "(included)" 184 | self.recording_trace = True 185 | 186 | Interpreter.run_JUMP(self) # run the jump normally so that we start the trace at the beginning of the loop 187 | recording_interpreter = RecordingInterpreter(self.pc, self.stack, self.code, self.loops, self.recording_trace, old_pc) 188 | try: 189 | print "Trace recording started at pc =", new_pc, "until (included) pc =", old_pc 190 | recording_interpreter.interpret() 191 | except TraceRecordingEnded: 192 | print "Trace recording ended!" 193 | self.pc = recording_interpreter.pc # the rest are mutable datastructures that were shared with the recording interp 194 | self.recording_trace = False 195 | 196 | loop_info['trace_id'], loop_info['trace'] = self.trace_id, recording_interpreter.trace 197 | self.trace_id += 1 198 | loop_info['has_trace'], loop_info['executable_trace'] = True, self.translate_trace(loop_info) 199 | 200 | print "Now jumping into compiled trace!" 201 | TracingInterpreter.run_JUMP(self) # recursive call, but this time it will run the compiled trace 202 | return 203 | 204 | else: 205 | self.loops[(new_pc, old_pc)] = {'hotness': 1, 'has_trace': False} 206 | self.recording_trace = False 207 | 208 | Interpreter.run_JUMP(self) 209 | 210 | class TraceRecordingEnded(Exception): 211 | pass 212 | 213 | TRACE_INSTR, TRACE_GUARD_GT_JUMP, TRACE_GUARD_GT_NOT_JUMP, TRACE_ENTER_TRACE = range(4) 214 | 215 | class GuardFailed(Exception): 216 | pass 217 | 218 | class RecordingInterpreter(TracingInterpreter): 219 | def __init__(self, pc, stack, code, loops, recording_trace, end_of_trace): 220 | self.trace = [] 221 | self.end_of_trace = end_of_trace 222 | 223 | TracingInterpreter.__init__(self, pc, stack, code, loops, recording_trace) 224 | 225 | def is_end_of_trace(self, current_pc): 226 | return current_pc == self.end_of_trace 227 | 228 | def run_PUSH(self): 229 | #print "Recording PUSH" 230 | self.trace.append( (TRACE_INSTR, self.code[self.pc], self.code[self.pc+1]) ) 231 | TracingInterpreter.run_PUSH(self) 232 | 233 | def run_ADD(self): 234 | #print "Recording ADD" 235 | self.trace.append( (TRACE_INSTR, self.code[self.pc], self.code[self.pc+1]) ) 236 | TracingInterpreter.run_ADD(self) 237 | 238 | def run_GT(self): 239 | #print "Recording GT" 240 | 241 | if self.stack[-1] > code[self.pc+1]: 242 | self.trace.append( (TRACE_GUARD_GT_JUMP, code[self.pc+1]) ) 243 | self.trace.append( (TRACE_INSTR, JUMP, code[self.pc+2]) ) 244 | else: 245 | self.trace.append( (TRACE_GUARD_GT_NOT_JUMP, code[self.pc+1]) ) 246 | self.trace.append( (TRACE_INSTR, JUMP, self.pc+3) ) 247 | 248 | TracingInterpreter.run_GT(self) 249 | 250 | def run_JUMP(self): 251 | end_of_trace = self.is_end_of_trace(self.pc) 252 | #print "Recording JUMP" 253 | self.trace.append( (TRACE_INSTR, self.code[self.pc], self.code[self.pc+1]) ) 254 | if end_of_trace: 255 | raise TraceRecordingEnded() 256 | 257 | TracingInterpreter.run_JUMP(self) 258 | 259 | def run_POP(self): 260 | #print "Recording POP" 261 | self.trace.append( (TRACE_INSTR, POP)) 262 | TracingInterpreter.run_POP(self) 263 | 264 | def enter_trace(self, loop_info): 265 | self.trace.append( (TRACE_ENTER_TRACE, loop_info) ) 266 | TracingInterpreter.enter_trace(self, loop_info) 267 | 268 | 269 | print TracingInterpreter(0, [], code, {}, False).interpret() 270 | #print Interpreter(0, [], code).interpret() 271 | --------------------------------------------------------------------------------