def check_dump_bytecode(self, code, expected, lineno=None): with contextlib.redirect_stdout(io.StringIO()) as stderr: if lineno is not None: bytecode.dump_bytecode(code, lineno=True) else: bytecode.dump_bytecode(code) output = stderr.getvalue() self.assertEqual(output, expected)
def test_type_validation(self): class T: first_lineno = 1 with self.assertRaises(TypeError): bytecode.dump_bytecode(T())
def emit_function(node: typing.Union[ast.AsyncFunctionDef, ast.FunctionDef, ast.Lambda], new_ctx: Context, is_async: bool): """ https://docs.python.org/3/library/dis.html#opcode-MAKE_FUNCTION MAKE_FUNCTION flags: 0x01 a tuple of default values for positional-only and positional-or-keyword parameters in positional order 0x02 a dictionary of keyword-only parameters’ default values 0x04 an annotation dictionary 0x08 a tuple containing cells for free variables, making a closure the code associated with the function (at TOS1) the qualified name of the function (at TOS) """ parent_ctx: Context = new_ctx.parent name = getattr(node, 'name', '<lambda>') new_ctx.bc.name = f'{parent_ctx.bc.name}.{name}' if parent_ctx.bc.name else name for decorator in getattr(node, 'decorator_list', ()): py_emit(decorator, parent_ctx) if is_async: new_ctx.bc.flags |= CompilerFlags.COROUTINE if isinstance(node, ast.Lambda): py_emit(node.body, new_ctx) new_ctx.bc.append(RETURN_VALUE(lineno=node.lineno)) else: head = node.body if isinstance(head, ast.Expr) and isinstance(head.value, ast.Str): new_ctx.bc.docstring = head.value.s for each in node.body: py_emit(each, new_ctx) args = node.args new_ctx.bc.argcount = len(args.args) new_ctx.bc.kwonlyargcount = len(args.kwonlyargs) make_function_flags = 0 if new_ctx.sym_tb.freevars: make_function_flags |= 0x08 if args.defaults: make_function_flags |= 0x01 if args.kw_defaults: make_function_flags |= 0x02 annotations = [] argnames = [] for arg in args.args: argnames.append(arg.arg) if arg.annotation: annotations.append((arg.arg, arg.annotation)) for arg in args.kwonlyargs: argnames.append(arg.arg) if arg.annotation: annotations.append((arg.arg, arg.annotation)) arg = args.vararg if arg: new_ctx.bc.flags |= CompilerFlags.VARARGS argnames.append(arg.arg) if arg.annotation: annotations.append((arg.arg, arg.annotation)) arg = args.kwarg if arg: new_ctx.bc.flags |= CompilerFlags.VARKEYWORDS argnames.append(arg.arg) if arg.annotation: annotations.append((arg.arg, arg.annotation)) if any(annotations): make_function_flags |= 0x04 new_ctx.bc.argnames.extend(argnames) if make_function_flags & 0x01: for each in args.defaults: py_emit(each, parent_ctx) parent_ctx.bc.append( Instr('BUILD_TUPLE', len(args.defaults), lineno=node.lineno)) if make_function_flags & 0x02: for each in args.kw_defaults: py_emit(each, parent_ctx) parent_ctx.bc.append( Instr('BUILD_TUPLE', len(args.kw_defaults), lineno=node.lineno)) if make_function_flags & 0x04: keys, annotation_values = zip(*annotations) for each in annotation_values: py_emit(each, parent_ctx) parent_ctx.bc.append( Instr('LOAD_CONST', tuple(keys), lineno=node.lineno)) parent_ctx.bc.append( Instr("BUILD_CONST_KEY_MAP", len(annotation_values), lineno=node.lineno)) if make_function_flags & 0x08: new_ctx.load_closure(lineno=node.lineno) new_ctx.bc.append(Instr('LOAD_CONST', None)) new_ctx.bc.append(Instr('RETURN_VALUE')) try: inner_code = new_ctx.bc.to_code() except RuntimeError: print(new_ctx.bc.filename) dump_bytecode(new_ctx.bc) raise parent_ctx.bc.append(Instr('LOAD_CONST', inner_code, lineno=node.lineno)) # when it comes to nested, the name is not generated correctly now. parent_ctx.bc.append( Instr('LOAD_CONST', new_ctx.bc.name, lineno=node.lineno)) parent_ctx.bc.append( Instr("MAKE_FUNCTION", make_function_flags, lineno=node.lineno)) parent_ctx.bc.extend([CALL_FUNCTION(1, lineno=node.lineno)] * len(getattr(node, 'decorator_list', ()))) if isinstance(node, ast.Lambda): pass else: parent_ctx.store_name(node.name, lineno=node.lineno)
def f(a, b): # res = a + b return def g(a, b): res = a + b if a < b else b + a r = 0 for a in range(res): r += 1 return r or 2 for x in (f, g): #get byte code for f dis(x) print(f.__code__.co_code) c = Bytecode.from_code(x.__code__) cc = ConcreteBytecode.from_code(x.__code__) dump_bytecode(c) dump_bytecode(cc) #generate byte code cnew = c.to_code() x.__code__ = cnew dis(x) print(x(3, 5))
# need to check the reurn value of the call, and return it directly before the finally block is setup yield calculate yield Instr('SETUP_FINALLY', return_finally) for instr in bytecode: yield instr # return value is now on the top of the stack, so we can cache it in postfix? yield return_finally yield Instr('DUP_TOP') # copy the return value to the top yield Instr('LOAD_CONST', postfix) # load a return function yield Instr('ROT_TWO') # swap so it's a call yield Instr('CALL_FUNCTION', 1) # invoke with the return value yield Instr('POP_TOP') yield Instr('END_FINALLY') bytecode[:] = list(generate_bytecodes(bytecode)) dump_bytecode(bytecode) my_function.__code__ = bytecode.to_code() my_function(1,2,3) import dis dis.dis(my_function.__code__)