Skip to content

Commit c08719d

Browse files
authored
[mypyc] Add debug op (and builder helper) for printing str or Value (#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.
1 parent 93d1ce4 commit c08719d

File tree

6 files changed

+54
-0
lines changed

6 files changed

+54
-0
lines changed

mypyc/irbuild/builder.py

+3
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,9 @@ def builtin_len(self, val: Value, line: int) -> Value:
420420
def new_tuple(self, items: list[Value], line: int) -> Value:
421421
return self.builder.new_tuple(items, line)
422422

423+
def debug_print(self, toprint: str | Value) -> None:
424+
return self.builder.debug_print(toprint)
425+
423426
# Helpers for IR building
424427

425428
def add_to_non_ext_dict(

mypyc/irbuild/ll_builder.py

+6
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@
162162
from mypyc.primitives.misc_ops import (
163163
bool_op,
164164
buf_init_item,
165+
debug_print_op,
165166
fast_isinstance_op,
166167
none_object_op,
167168
not_implemented_op,
@@ -300,6 +301,11 @@ def flush_keep_alives(self) -> None:
300301
self.add(KeepAlive(self.keep_alives.copy()))
301302
self.keep_alives = []
302303

304+
def debug_print(self, toprint: str | Value) -> None:
305+
if isinstance(toprint, str):
306+
toprint = self.load_str(toprint)
307+
self.primitive_op(debug_print_op, [toprint], -1)
308+
303309
# Type conversions
304310

305311
def box(self, src: Value) -> Value:

mypyc/lib-rt/CPy.h

+1
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,7 @@ PyObject *CPyPickle_SetState(PyObject *obj, PyObject *state);
866866
PyObject *CPyPickle_GetState(PyObject *obj);
867867
CPyTagged CPyTagged_Id(PyObject *o);
868868
void CPyDebug_Print(const char *msg);
869+
void CPyDebug_PrintObject(PyObject *obj);
869870
void CPy_Init(void);
870871
int CPyArg_ParseTupleAndKeywords(PyObject *, PyObject *,
871872
const char *, const char *, const char * const *, ...);

mypyc/lib-rt/misc_ops.c

+16
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,22 @@ void CPyDebug_Print(const char *msg) {
535535
fflush(stdout);
536536
}
537537

538+
void CPyDebug_PrintObject(PyObject *obj) {
539+
// Printing can cause errors. We don't want this to affect any existing
540+
// state so we'll save any existing error and restore it at the end.
541+
PyObject *exc_type, *exc_value, *exc_traceback;
542+
PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
543+
544+
if (PyObject_Print(obj, stderr, 0) == -1) {
545+
PyErr_Print();
546+
} else {
547+
fprintf(stderr, "\n");
548+
}
549+
fflush(stderr);
550+
551+
PyErr_Restore(exc_type, exc_value, exc_traceback);
552+
}
553+
538554
int CPySequence_CheckUnpackCount(PyObject *sequence, Py_ssize_t expected) {
539555
Py_ssize_t actual = Py_SIZE(sequence);
540556
if (unlikely(actual != expected)) {

mypyc/primitives/misc_ops.py

+8
Original file line numberDiff line numberDiff line change
@@ -283,3 +283,11 @@
283283
return_type=void_rtype,
284284
error_kind=ERR_NEVER,
285285
)
286+
287+
debug_print_op = custom_primitive_op(
288+
name="debug_print",
289+
c_function_name="CPyDebug_PrintObject",
290+
arg_types=[object_rprimitive],
291+
return_type=void_rtype,
292+
error_kind=ERR_NEVER,
293+
)

mypyc/test/test_misc.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from __future__ import annotations
2+
3+
import unittest
4+
5+
from mypyc.ir.ops import BasicBlock
6+
from mypyc.ir.pprint import format_blocks, generate_names_for_ir
7+
from mypyc.irbuild.ll_builder import LowLevelIRBuilder
8+
from mypyc.options import CompilerOptions
9+
10+
11+
class TestMisc(unittest.TestCase):
12+
def test_debug_op(self) -> None:
13+
block = BasicBlock()
14+
builder = LowLevelIRBuilder(errors=None, options=CompilerOptions())
15+
builder.activate_block(block)
16+
builder.debug_print("foo")
17+
18+
names = generate_names_for_ir([], [block])
19+
code = format_blocks([block], names, {})
20+
assert code[:-1] == ["L0:", " r0 = 'foo'", " CPyDebug_PrintObject(r0)"]

0 commit comments

Comments
 (0)