File: log_trace.py

package info (click to toggle)
mypy 1.19.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 22,412 kB
  • sloc: python: 114,754; ansic: 13,343; cpp: 11,380; makefile: 257; sh: 28
file content (158 lines) | stat: -rw-r--r-- 5,374 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
"""This optional pass adds logging of various executed operations.

Some subset of the executed operations are logged to the mypyc_trace.txt file.

This is useful for performance analysis. For example, it's possible
to identify how frequently various primitive functions are called,
and in which code locations they are called.
"""

from __future__ import annotations

from typing import Final

from mypyc.ir.func_ir import FuncIR
from mypyc.ir.ops import (
    Box,
    Call,
    CallC,
    Cast,
    CString,
    DecRef,
    GetAttr,
    IncRef,
    LoadLiteral,
    LoadStatic,
    Op,
    PrimitiveOp,
    SetAttr,
    Unbox,
    Value,
)
from mypyc.ir.rtypes import none_rprimitive
from mypyc.irbuild.ll_builder import LowLevelIRBuilder
from mypyc.options import CompilerOptions
from mypyc.primitives.misc_ops import log_trace_event
from mypyc.transform.ir_transform import IRTransform


def insert_event_trace_logging(fn: FuncIR, options: CompilerOptions) -> None:
    builder = LowLevelIRBuilder(None, options)
    transform = LogTraceEventTransform(builder, fn.decl.fullname)
    transform.transform_blocks(fn.blocks)
    fn.blocks = builder.blocks


def get_load_global_name(op: CallC) -> str | None:
    name = op.function_name
    if name == "CPyDict_GetItem":
        arg = op.args[0]
        if (
            isinstance(arg, LoadStatic)
            and arg.namespace == "static"
            and arg.identifier == "globals"
            and isinstance(op.args[1], LoadLiteral)
        ):
            return str(op.args[1].value)
    return None


# These primitives perform an implicit IncRef for the return value. Only some of the most common ones
# are included, and mostly ops that could be switched to use borrowing in some contexts.
primitives_that_inc_ref: Final = {
    "list_get_item_unsafe",
    "CPyList_GetItemShort",
    "CPyDict_GetWithNone",
    "CPyList_GetItem",
    "CPyDict_GetItem",
    "CPyList_PopLast",
}


class LogTraceEventTransform(IRTransform):
    def __init__(self, builder: LowLevelIRBuilder, fullname: str) -> None:
        super().__init__(builder)
        self.fullname = fullname.encode("utf-8")

    def visit_call(self, op: Call) -> Value:
        # TODO: Use different op name when constructing an instance
        return self.log(op, "call", op.fn.fullname)

    def visit_primitive_op(self, op: PrimitiveOp) -> Value:
        value = self.log(op, "primitive_op", op.desc.name)
        if op.desc.name in primitives_that_inc_ref:
            self.log_inc_ref(value)
        return value

    def visit_call_c(self, op: CallC) -> Value:
        if global_name := get_load_global_name(op):
            return self.log(op, "globals_dict_get_item", global_name)

        func_name = op.function_name
        if func_name == "PyObject_Vectorcall" and isinstance(op.args[0], CallC):
            if global_name := get_load_global_name(op.args[0]):
                return self.log(op, "python_call_global", global_name)
        elif func_name == "CPyObject_GetAttr" and isinstance(op.args[1], LoadLiteral):
            return self.log(op, "python_get_attr", str(op.args[1].value))
        elif func_name == "PyObject_VectorcallMethod" and isinstance(op.args[0], LoadLiteral):
            return self.log(op, "python_call_method", str(op.args[0].value))

        value = self.log(op, "call_c", func_name)
        if func_name in primitives_that_inc_ref:
            self.log_inc_ref(value)
        return value

    def visit_get_attr(self, op: GetAttr) -> Value:
        value = self.log(op, "get_attr", f"{op.class_type.name}.{op.attr}")
        if not op.is_borrowed and op.type.is_refcounted:
            self.log_inc_ref(op)
        return value

    def visit_set_attr(self, op: SetAttr) -> Value:
        name = "set_attr" if not op.is_init else "set_attr_init"
        return self.log(op, name, f"{op.class_type.name}.{op.attr}")

    def visit_box(self, op: Box) -> Value:
        if op.src.type is none_rprimitive:
            # Boxing 'None' is a very quick operation, so we don't log it.
            return self.add(op)
        else:
            return self.log(op, "box", str(op.src.type))

    def visit_unbox(self, op: Unbox) -> Value:
        return self.log(op, "unbox", str(op.type))

    def visit_cast(self, op: Cast) -> Value | None:
        value = self.log(op, "cast", str(op.type))
        if not op.is_borrowed:
            self.log_inc_ref(value)
        return value

    def visit_inc_ref(self, op: IncRef) -> Value:
        return self.log(op, "inc_ref", str(op.src.type))

    def visit_dec_ref(self, op: DecRef) -> Value:
        return self.log(op, "dec_ref", str(op.src.type))

    def log_inc_ref(self, value: Value) -> None:
        self.log_event("inc_ref", str(value.type), value.line)

    def log(self, op: Op, name: str, details: str) -> Value:
        self.log_event(name, details, op.line)
        return self.add(op)

    def log_event(self, name: str, details: str, line: int) -> None:
        if line >= 0:
            line_str = str(line)
        else:
            line_str = ""
        self.builder.primitive_op(
            log_trace_event,
            [
                CString(self.fullname),
                CString(line_str.encode("ascii")),
                CString(name.encode("utf-8")),
                CString(details.encode("utf-8")),
            ],
            line,
        )