Skip to content

Commit

Permalink
[mypyc] Add debug op (and builder helper) for printing str or Value (#…
Browse files Browse the repository at this point in the history
…18552)

It generates C code to print to stdout, but tries to preserve the error
state and not affect the code it's added to.

Added test for it, but also tested this by adding
`builder.debug_print(typ)` in `add_non_ext_class_attr_ann` and it prints
the class name.
It's also useful to use it like `builder.debug_print("MARKER")` and then
to search in the generated C code for MARKER. For more complex debugging
tasks, this is useful in finding your way around the generated C code
and quickly looking at the interesting part.

I saw that there's already a misc op `CPyDebug_Print`. I haven't seen it
used though. I think we can remove that one if this is proving useful.
  • Loading branch information
svalentin authored Jan 28, 2025
1 parent 93d1ce4 commit c08719d
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 0 deletions.
3 changes: 3 additions & 0 deletions mypyc/irbuild/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,9 @@ def builtin_len(self, val: Value, line: int) -> Value:
def new_tuple(self, items: list[Value], line: int) -> Value:
return self.builder.new_tuple(items, line)

def debug_print(self, toprint: str | Value) -> None:
return self.builder.debug_print(toprint)

# Helpers for IR building

def add_to_non_ext_dict(
Expand Down
6 changes: 6 additions & 0 deletions mypyc/irbuild/ll_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
from mypyc.primitives.misc_ops import (
bool_op,
buf_init_item,
debug_print_op,
fast_isinstance_op,
none_object_op,
not_implemented_op,
Expand Down Expand Up @@ -300,6 +301,11 @@ def flush_keep_alives(self) -> None:
self.add(KeepAlive(self.keep_alives.copy()))
self.keep_alives = []

def debug_print(self, toprint: str | Value) -> None:
if isinstance(toprint, str):
toprint = self.load_str(toprint)
self.primitive_op(debug_print_op, [toprint], -1)

# Type conversions

def box(self, src: Value) -> Value:
Expand Down
1 change: 1 addition & 0 deletions mypyc/lib-rt/CPy.h
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,7 @@ PyObject *CPyPickle_SetState(PyObject *obj, PyObject *state);
PyObject *CPyPickle_GetState(PyObject *obj);
CPyTagged CPyTagged_Id(PyObject *o);
void CPyDebug_Print(const char *msg);
void CPyDebug_PrintObject(PyObject *obj);
void CPy_Init(void);
int CPyArg_ParseTupleAndKeywords(PyObject *, PyObject *,
const char *, const char *, const char * const *, ...);
Expand Down
16 changes: 16 additions & 0 deletions mypyc/lib-rt/misc_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,22 @@ void CPyDebug_Print(const char *msg) {
fflush(stdout);
}

void CPyDebug_PrintObject(PyObject *obj) {
// Printing can cause errors. We don't want this to affect any existing
// state so we'll save any existing error and restore it at the end.
PyObject *exc_type, *exc_value, *exc_traceback;
PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);

if (PyObject_Print(obj, stderr, 0) == -1) {
PyErr_Print();
} else {
fprintf(stderr, "\n");
}
fflush(stderr);

PyErr_Restore(exc_type, exc_value, exc_traceback);
}

int CPySequence_CheckUnpackCount(PyObject *sequence, Py_ssize_t expected) {
Py_ssize_t actual = Py_SIZE(sequence);
if (unlikely(actual != expected)) {
Expand Down
8 changes: 8 additions & 0 deletions mypyc/primitives/misc_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,11 @@
return_type=void_rtype,
error_kind=ERR_NEVER,
)

debug_print_op = custom_primitive_op(
name="debug_print",
c_function_name="CPyDebug_PrintObject",
arg_types=[object_rprimitive],
return_type=void_rtype,
error_kind=ERR_NEVER,
)
20 changes: 20 additions & 0 deletions mypyc/test/test_misc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from __future__ import annotations

import unittest

from mypyc.ir.ops import BasicBlock
from mypyc.ir.pprint import format_blocks, generate_names_for_ir
from mypyc.irbuild.ll_builder import LowLevelIRBuilder
from mypyc.options import CompilerOptions


class TestMisc(unittest.TestCase):
def test_debug_op(self) -> None:
block = BasicBlock()
builder = LowLevelIRBuilder(errors=None, options=CompilerOptions())
builder.activate_block(block)
builder.debug_print("foo")

names = generate_names_for_ir([], [block])
code = format_blocks([block], names, {})
assert code[:-1] == ["L0:", " r0 = 'foo'", " CPyDebug_PrintObject(r0)"]

0 comments on commit c08719d

Please sign in to comment.