| 
 | 1 | +"""This optional pass adds logging of various executed operations.  | 
 | 2 | +
  | 
 | 3 | +Some subset of the executed operations are logged to the mypyc_trace.txt file.  | 
 | 4 | +
  | 
 | 5 | +This is useful for performance analysis. For example, it's possible  | 
 | 6 | +to identify how frequently various primitive functions are called,  | 
 | 7 | +and in which code locations they are called.  | 
 | 8 | +"""  | 
 | 9 | + | 
 | 10 | +from __future__ import annotations  | 
 | 11 | + | 
 | 12 | +from mypyc.ir.func_ir import FuncIR  | 
 | 13 | +from mypyc.ir.ops import Call, CallC, CString, LoadLiteral, LoadStatic, Op, PrimitiveOp, Value  | 
 | 14 | +from mypyc.irbuild.ll_builder import LowLevelIRBuilder  | 
 | 15 | +from mypyc.options import CompilerOptions  | 
 | 16 | +from mypyc.primitives.misc_ops import log_trace_event  | 
 | 17 | +from mypyc.transform.ir_transform import IRTransform  | 
 | 18 | + | 
 | 19 | + | 
 | 20 | +def insert_event_trace_logging(fn: FuncIR, options: CompilerOptions) -> None:  | 
 | 21 | +    builder = LowLevelIRBuilder(None, options)  | 
 | 22 | +    transform = LogTraceEventTransform(builder, fn.decl.fullname)  | 
 | 23 | +    transform.transform_blocks(fn.blocks)  | 
 | 24 | +    fn.blocks = builder.blocks  | 
 | 25 | + | 
 | 26 | + | 
 | 27 | +def get_load_global_name(op: CallC) -> str | None:  | 
 | 28 | +    name = op.function_name  | 
 | 29 | +    if name == "CPyDict_GetItem":  | 
 | 30 | +        arg = op.args[0]  | 
 | 31 | +        if (  | 
 | 32 | +            isinstance(arg, LoadStatic)  | 
 | 33 | +            and arg.namespace == "static"  | 
 | 34 | +            and arg.identifier == "globals"  | 
 | 35 | +            and isinstance(op.args[1], LoadLiteral)  | 
 | 36 | +        ):  | 
 | 37 | +            return str(op.args[1].value)  | 
 | 38 | +    return None  | 
 | 39 | + | 
 | 40 | + | 
 | 41 | +class LogTraceEventTransform(IRTransform):  | 
 | 42 | +    def __init__(self, builder: LowLevelIRBuilder, fullname: str) -> None:  | 
 | 43 | +        super().__init__(builder)  | 
 | 44 | +        self.fullname = fullname.encode("utf-8")  | 
 | 45 | + | 
 | 46 | +    def visit_call(self, op: Call) -> Value:  | 
 | 47 | +        # TODO: Use different op name when constructing an instance  | 
 | 48 | +        return self.log(op, "call", op.fn.fullname)  | 
 | 49 | + | 
 | 50 | +    def visit_primitive_op(self, op: PrimitiveOp) -> Value:  | 
 | 51 | +        return self.log(op, "primitive_op", op.desc.name)  | 
 | 52 | + | 
 | 53 | +    def visit_call_c(self, op: CallC) -> Value:  | 
 | 54 | +        if global_name := get_load_global_name(op):  | 
 | 55 | +            return self.log(op, "globals_dict_get_item", global_name)  | 
 | 56 | + | 
 | 57 | +        func_name = op.function_name  | 
 | 58 | +        if func_name == "PyObject_Vectorcall" and isinstance(op.args[0], CallC):  | 
 | 59 | +            if global_name := get_load_global_name(op.args[0]):  | 
 | 60 | +                return self.log(op, "python_call_global", global_name)  | 
 | 61 | +        elif func_name == "CPyObject_GetAttr" and isinstance(op.args[1], LoadLiteral):  | 
 | 62 | +            return self.log(op, "python_get_attr", str(op.args[1].value))  | 
 | 63 | +        elif func_name == "PyObject_VectorcallMethod" and isinstance(op.args[0], LoadLiteral):  | 
 | 64 | +            return self.log(op, "python_call_method", str(op.args[0].value))  | 
 | 65 | + | 
 | 66 | +        return self.log(op, "call_c", func_name)  | 
 | 67 | + | 
 | 68 | +    def log(self, op: Op, name: str, details: str) -> Value:  | 
 | 69 | +        if op.line >= 0:  | 
 | 70 | +            line_str = str(op.line)  | 
 | 71 | +        else:  | 
 | 72 | +            line_str = ""  | 
 | 73 | +        self.builder.primitive_op(  | 
 | 74 | +            log_trace_event,  | 
 | 75 | +            [  | 
 | 76 | +                CString(self.fullname),  | 
 | 77 | +                CString(line_str.encode("ascii")),  | 
 | 78 | +                CString(name.encode("utf-8")),  | 
 | 79 | +                CString(details.encode("utf-8")),  | 
 | 80 | +            ],  | 
 | 81 | +            op.line,  | 
 | 82 | +        )  | 
 | 83 | +        return self.add(op)  | 
0 commit comments