[Mlir-commits] [mlir] [MLIR] Auto generate source location for python bindings (PR #112923)

Maksim Levental llvmlistbot at llvm.org
Sat Jul 12 08:41:49 PDT 2025


makslevental wrote:

I have another idea: https://docs.python.org/3/library/sys.html#sys.setprofile. Here's an example snippet:

```python
def my_profiler(frame, event, arg):
    if event == "call":
        frame_info = inspect.getframeinfo(frame)
        print(
            f"CALL: Function '{frame.f_code.co_name}' called in '{frame.f_code.co_filename}' at line {frame.f_lineno} at col: {frame_info.positions.col_offset}"
        )

import sys
sys.setprofile(my_profiler)
```

I dropped this into a random script and got prints like

```
CALL: Function 'yield_' called in '/home/mlevental/dev_projects/mlir-python-extras/mlir/extras/dialects/ext/scf.py' at line 368 at col: 0
CALL: Function 'yield_' called in '/home/mlevental/miniconda3/envs/mlir-python-extras-py.313/lib/python3.12/site-packages/mlir/dialects/_scf_ops_gen.py' at line 532 at col: 0
CALL: Function '__init__' called in '/home/mlevental/miniconda3/envs/mlir-python-extras-py.313/lib/python3.12/site-packages/mlir/dialects/_scf_ops_gen.py' at line 517 at col: 0
```

So the idea would be to basically keep a window of these locations:

```python

prev_locations = deque()

def location_tracker(frame, event, arg):
    if event == "call":
        frame_info = inspect.getframeinfo(frame)
        prev_locations.append((frame.f_code.co_name, frame.f_code.co_filename, frame.f_lineno, frame_info.positions.col_offset))
        if len(prev_locations) > 3:
            prev_locations.popleft()

import sys
sys.setprofile(location_tracker)
```

and then query them in the `__init__`s/builders:

```python
if loc is None:
    # find the most recent entry in prev_locations that is not within `_op_gen.py`
```

Note, obvious using `deque` is silly here (we can use `prev_loc1`, `prev_loc2`, etc and just update them round-robin).
Note, also, https://docs.python.org/3/library/threading.html#threading.setprofile also exists and is probably the better approach given we're moving to a "free threading" world soon-ish.

The advantage of this approach is that it does not force walk the frame stack each time we want to get a location. Naturally the downside is we're recording a window of the frame stack on every single call and that's high overhead.

**Here's the good part**: lucky for us, as of py3.12, there's a much faster way to do this kind of monitoring: https://peps.python.org/pep-0669/. 

So this works just like `my_profiler` above:

```python
sys.monitoring.use_tool_id(sys.monitoring.PROFILER_ID, "location_tracker")

def my_profiler_fast(code: CodeType, instruction_offset: int, callable: object, arg0: object):
    print(code.co_name, code.co_filename, code.co_firstlineno)

sys.monitoring.set_events(sys.monitoring.PROFILER_ID, sys.monitoring.events.CALL)
sys.monitoring.register_callback(
    sys.monitoring.PROFILER_ID, sys.monitoring.events.CALL, my_profiler
)
```
...
```
yield_ /home/mlevental/dev_projects/mlir-python-extras/mlir/extras/dialects/ext/scf.py 368
yield_ /home/mlevental/miniconda3/envs/mlir-python-extras-py.313/lib/python3.12/site-packages/mlir/dialects/_scf_ops_gen.py 532
__init__ /home/mlevental/miniconda3/envs/mlir-python-extras-py.313/lib/python3.12/site-packages/mlir/dialects/_scf_ops_gen.py 517
```

but is much faster (very evident from trying both).

So we could implement both of these (use a branch like `if sys.version_info.minor < 12: register_slow else: register_fast`), have them both off by default but togglable and then eventually (couple of years?) remove the slow path.

What do you guys think?

https://github.com/llvm/llvm-project/pull/112923


More information about the Mlir-commits mailing list