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,
)
|