def persistent_locals(f): """Function decorator to expose local variables after execution. Modify the function such that, at the exit of the function (regular exit or exceptions), the local dictionary is copied to a read-only function property 'locals'. This decorator wraps the function in a callable object, and modifies its bytecode by adding an external try...finally statement equivalent to the following: def f(self, *args, **kwargs): try: ... old code ... finally: self._locals = locals().copy() del self._locals['self'] """ # ### disassemble f f_code = bp.Code.from_code(f.func_code) # ### use bytecode injection to add try...finally statement around code finally_label = bp.Label() # try: code_before = (bp.SETUP_FINALLY, finally_label) # [original code here] # finally: code_after = [ (finally_label, None), # self._locals = locals().copy() (bp.LOAD_GLOBAL, 'locals'), (bp.CALL_FUNCTION, 0), (bp.LOAD_ATTR, 'copy'), (bp.CALL_FUNCTION, 0), (bp.LOAD_FAST, 'self'), (bp.STORE_ATTR, '_locals'), # del self._locals['self'] (bp.LOAD_FAST, 'self'), (bp.LOAD_ATTR, '_locals'), (bp.LOAD_CONST, 'self'), (bp.DELETE_SUBSCR, None), (bp.END_FINALLY, None), (bp.LOAD_CONST, None), (bp.RETURN_VALUE, None) ] f_code.code.insert(0, code_before) f_code.code.extend(code_after) # ### re-assemble f_code.args = ('self', ) + f_code.args func = new.function(f_code.to_code(), f.func_globals, f.func_name, f.func_defaults, f.func_closure) return PersistentLocalsFunction(func)
def boolean_and(): # a b -- (b and a) # TODO: Our other lang requires these are booleans # TODO: Use JUMP_IF_FALSE_OR_POP and leave either a or b label_out = bp.Label() return [ (bp.POP_JUMP_IF_TRUE, label_out), # a b -- a (bp.POP_TOP, None), (bp.LOAD_CONST, False), (label_out, None), ]
def if_stmt(): # Stack: (p)redicate (c)onsequent (a)lternative # Example: "true 'yes' 'no' if" ==> 'yes' # Example: "false 'yes' 'no' if" ==> 'no' pop = bp.Label() return [ (bp.ROT_THREE, None), # p c a -- a p c (bp.ROT_TWO, None), # a p c -- a c p (bp.POP_JUMP_IF_FALSE, pop), (bp.ROT_TWO, None), # a c -- c a (pop, None), (bp.POP_TOP, None), ]
def end_loop(startlabel): endlabel = bp.Label() # Update goto end-of-loop label start = labelpos[startlabel] c[start] = (bp.POP_JUMP_IF_TRUE, endlabel) c.append((endlabel, None)) c.append((bp.LOAD_FAST, "memory")) c.append((bp.LOAD_FAST, "ptr")) c.append((bp.BINARY_SUBSCR, None)) c.append((bp.LOAD_CONST, 0)) c.append((bp.COMPARE_OP, "==")) c.append((bp.POP_JUMP_IF_FALSE, startlabel))
def compile(source, memsize=300000, flush=True, modulus=None, verbose=False): # Bytecode c = [] # Keep track of jump labels labels = [] labelpos = {} # import sys c.append((bp.LOAD_CONST, -1)) c.append((bp.LOAD_CONST, None)) c.append((bp.IMPORT_NAME, "sys")) c.append((bp.STORE_FAST, "sys")) # memory = [0]*memsize c.append((bp.LOAD_CONST, 0)) c.append((bp.BUILD_LIST, 1)) c.append((bp.LOAD_CONST, memsize)) c.append((bp.BINARY_MULTIPLY, None)) c.append((bp.STORE_FAST, "memory")) # ptr = 0 c.append((bp.LOAD_CONST, 0)) c.append((bp.STORE_FAST, "ptr")) def add(value): if modulus is None: c.append((bp.LOAD_FAST, "memory")) c.append((bp.LOAD_FAST, "ptr")) c.append((bp.DUP_TOPX, 2)) c.append((bp.BINARY_SUBSCR, None)) c.append((bp.LOAD_CONST, value)) c.append((bp.INPLACE_ADD, None)) c.append((bp.ROT_THREE, None)) c.append((bp.STORE_SUBSCR, None)) else: c.append((bp.LOAD_FAST, "memory")) c.append((bp.LOAD_FAST, "ptr")) c.append((bp.BINARY_SUBSCR, None)) c.append((bp.LOAD_CONST, value)) c.append((bp.BINARY_ADD, None)) c.append((bp.LOAD_CONST, modulus)) c.append((bp.BINARY_MODULO, None)) c.append((bp.LOAD_FAST, "memory")) c.append((bp.LOAD_FAST, "ptr")) c.append((bp.STORE_SUBSCR, None)) def zero(): c.append((bp.LOAD_CONST, 0)) c.append((bp.LOAD_FAST, "memory")) c.append((bp.LOAD_FAST, "ptr")) c.append((bp.STORE_SUBSCR, None)) def dot(count): # Prepare call to sys.stdout.write(chr(...)) c.append((bp.LOAD_GLOBAL, "sys")) c.append((bp.LOAD_ATTR, "stdout")) c.append((bp.LOAD_ATTR, "write")) c.append((bp.LOAD_GLOBAL, "chr")) # Get value c.append((bp.LOAD_FAST, "memory")) c.append((bp.LOAD_FAST, "ptr")) c.append((bp.BINARY_SUBSCR, None)) # Call chr c.append((bp.CALL_FUNCTION, 1)) if count > 1: c.append((bp.LOAD_CONST, count)) c.append((bp.BINARY_MULTIPLY, None)) # Call sys.stdout.write and drop its return value c.append((bp.CALL_FUNCTION, 1)) c.append((bp.POP_TOP, None)) if flush: c.append((bp.LOAD_GLOBAL, "sys")) c.append((bp.LOAD_ATTR, "stdout")) c.append((bp.LOAD_ATTR, "flush")) c.append((bp.CALL_FUNCTION, 0)) c.append((bp.POP_TOP, None)) def comma(count): c.append((bp.LOAD_GLOBAL, "ord")) c.append((bp.LOAD_FAST, "sys")) c.append((bp.LOAD_ATTR, "stdin")) c.append((bp.LOAD_ATTR, "read")) c.append((bp.LOAD_CONST, count)) c.append((bp.CALL_FUNCTION, 1)) c.append((bp.CALL_FUNCTION, 1)) c.append((bp.LOAD_FAST, "memory")) c.append((bp.LOAD_FAST, "ptr")) c.append((bp.STORE_SUBSCR, None)) def move(amount): c.append((bp.LOAD_FAST, "ptr")) c.append((bp.LOAD_CONST, amount)) c.append((bp.INPLACE_ADD, None)) c.append((bp.STORE_FAST, "ptr")) def start_loop(label): c.append((label, None)) c.append((bp.LOAD_FAST, "memory")) c.append((bp.LOAD_FAST, "ptr")) c.append((bp.BINARY_SUBSCR, None)) c.append((bp.LOAD_CONST, 0)) c.append((bp.COMPARE_OP, "==")) # We don't know the label of the end-of-loop position, so store a # temporary marker and get back to it later c.append((bp.POP_JUMP_IF_TRUE, None)) labelpos[label] = len(c)-1 def end_loop(startlabel): endlabel = bp.Label() # Update goto end-of-loop label start = labelpos[startlabel] c[start] = (bp.POP_JUMP_IF_TRUE, endlabel) c.append((endlabel, None)) c.append((bp.LOAD_FAST, "memory")) c.append((bp.LOAD_FAST, "ptr")) c.append((bp.BINARY_SUBSCR, None)) c.append((bp.LOAD_CONST, 0)) c.append((bp.COMPARE_OP, "==")) c.append((bp.POP_JUMP_IF_FALSE, startlabel)) # Translate Brainfuck to Python bytecode for (op, count) in optimize_source(source, verbose=verbose): if op == ">": move(count) elif op == "<": move(-count) elif op == "+": add(count) elif op == "-": add(-count) elif op == ".": dot(count) elif op == ",": comma(count) elif op == "[": labels.append(bp.Label()) start_loop(labels[-1]) elif op == "]": end_loop(labels.pop()) elif op == "zero": zero() elif op is None: pass else: print("Unknown operator: %s" % op) sys.exit(1) # return None c.append((bp.LOAD_CONST, None)) c.append((bp.RETURN_VALUE, None)) return c
def create_head_hook(state, loop_end_label): vars_storage = state.vars_storage real_vars_indexes = state.real_vars_indexes manual_store_counter = state.manual_store_counter unpacked_straight = [] for index in real_vars_indexes: unpacked_straight.append(vars_storage[index]) if manual_store_counter is not None: unpacked_straight.append(manual_store_counter) content = [ (byteplay.DUP_TOP, None), (byteplay.LOAD_CONST, exec_loop), (byteplay.ROT_TWO, None), (byteplay.LOAD_CONST, state.settings), (byteplay.LOAD_CONST, state.content), (byteplay.LOAD_CONST, vars_storage), (byteplay.LOAD_CONST, real_vars_indexes), (byteplay.LOAD_CONST, manual_store_counter is not None), (byteplay.LOAD_CONST, globals), (byteplay.CALL_FUNCTION, 0), (byteplay.LOAD_CONST, locals), (byteplay.CALL_FUNCTION, 0), (byteplay.BUILD_LIST, 0), (byteplay.DUP_TOP, None), (byteplay.STORE_FAST, state.real_folded_arr), ] if python_version < (2, 7): for folded_code in state.consts: content += [ (byteplay.DUP_TOP, None), ] + folded_code + [ (byteplay.LIST_APPEND, None), ] else: for folded_code in state.consts: content += folded_code + [ (byteplay.LIST_APPEND, 1), ] content += [ (byteplay.CALL_FUNCTION, 9), (byteplay.DELETE_FAST, state.real_folded_arr), (byteplay.DUP_TOP, None), (byteplay.LOAD_CONST, None), ] head_end_label = byteplay.Label() # Let's "res" is a return value of "exec_loop". # Now the stack looks like: # None, res, res, iterator, ... content += [ (byteplay.COMPARE_OP, 'is not'), ] if python_version < (2, 7): content += [ (byteplay.JUMP_IF_FALSE, head_end_label), (byteplay.POP_TOP, None), ] else: content += [ (byteplay.POP_JUMP_IF_FALSE, head_end_label), ] # Code below runs if res is not None (optimization was successful). # Now the stack looks like: # res, iterator, ... content += [ (byteplay.UNPACK_SEQUENCE, len(unpacked_straight)), ] # Store changed variables for straight in unpacked_straight: content.append((vars_opers_map[straight[0]][1], straight[1])) # We need to pop the iterator because the loop will be skipped content += [ (byteplay.POP_TOP, None), (byteplay.JUMP_ABSOLUTE, loop_end_label), ] # Code below runs if res is None (optimization failed). # Now the stack looks like: # None, iterator, ... (in Python 2.7) # False, None, iterator, ... (in Python < 2.7) content += [ (head_end_label, None), (byteplay.POP_TOP, None), ] if python_version < (2, 7): content += [ (byteplay.POP_TOP, None), ] # Right before the loop (before GET_ITER instruction) iterator # must be at the top of the stack. return content