Skip to content

py/objcode: Implement PEP626 to add co_lines to code objects on settrace builds. #16989

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 26 additions & 16 deletions py/bc.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,25 +308,35 @@ static inline void mp_module_context_alloc_tables(mp_module_context_t *context,
#endif
}

typedef struct _mp_code_lineinfo_t {
size_t bc_increment;
size_t line_increment;
} mp_code_lineinfo_t;

static inline mp_code_lineinfo_t mp_bytecode_decode_lineinfo(const byte **line_info) {
mp_code_lineinfo_t result;
size_t c = (*line_info)[0];
if ((c & 0x80) == 0) {
// 0b0LLBBBBB encoding
result.bc_increment = c & 0x1f;
result.line_increment = c >> 5;
*line_info += 1;
} else {
// 0b1LLLBBBB 0bLLLLLLLL encoding (l's LSB in second byte)
result.bc_increment = c & 0xf;
result.line_increment = ((c << 4) & 0x700) | (*line_info)[1];
*line_info += 2;
}
return result;
}

static inline size_t mp_bytecode_get_source_line(const byte *line_info, const byte *line_info_top, size_t bc_offset) {
size_t source_line = 1;
while (line_info < line_info_top) {
size_t c = *line_info;
size_t b, l;
if ((c & 0x80) == 0) {
// 0b0LLBBBBB encoding
b = c & 0x1f;
l = c >> 5;
line_info += 1;
} else {
// 0b1LLLBBBB 0bLLLLLLLL encoding (l's LSB in second byte)
b = c & 0xf;
l = ((c << 4) & 0x700) | line_info[1];
line_info += 2;
}
if (bc_offset >= b) {
bc_offset -= b;
source_line += l;
mp_code_lineinfo_t decoded = mp_bytecode_decode_lineinfo(&line_info);
if (bc_offset >= decoded.bc_increment) {
bc_offset -= decoded.bc_increment;
source_line += decoded.line_increment;
} else {
// found source line corresponding to bytecode offset
break;
Expand Down
65 changes: 65 additions & 0 deletions py/objcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,67 @@ static mp_obj_t raw_code_lnotab(const mp_raw_code_t *rc) {
return o;
}

static mp_obj_t code_colines_iter(mp_obj_t);
static mp_obj_t code_colines_next(mp_obj_t);
typedef struct _mp_obj_colines_iter_t {
mp_obj_base_t base;
mp_fun_1_t iternext;
const mp_raw_code_t *rc;
mp_uint_t bc;
mp_uint_t source_line;
const byte *ci;
} mp_obj_colines_iter_t;

static mp_obj_t code_colines_iter(mp_obj_t self_in) {
mp_obj_code_t *self = MP_OBJ_TO_PTR(self_in);
mp_obj_colines_iter_t *iter = mp_obj_malloc(mp_obj_colines_iter_t, &mp_type_polymorph_iter);
iter->iternext = code_colines_next;
iter->rc = self->rc;
iter->bc = 0;
iter->source_line = 1;
iter->ci = self->rc->prelude.line_info;
return MP_OBJ_FROM_PTR(iter);
}
static MP_DEFINE_CONST_FUN_OBJ_1(code_colines_obj, code_colines_iter);

static mp_obj_t code_colines_next(mp_obj_t iter_in) {
mp_obj_colines_iter_t *iter = MP_OBJ_TO_PTR(iter_in);
const byte *ci_end = iter->rc->prelude.line_info_top;

mp_uint_t start = iter->bc;
mp_uint_t line_no = iter->source_line;
bool another = true;

while (another && iter->ci < ci_end) {
another = false;
mp_code_lineinfo_t decoded = mp_bytecode_decode_lineinfo(&iter->ci);
iter->bc += decoded.bc_increment;
iter->source_line += decoded.line_increment;

if (decoded.bc_increment == 0) {
line_no = iter->source_line;
another = true;
} else if (decoded.line_increment == 0) {
another = true;
}
}

if (another) {
mp_uint_t prelude_size = (iter->rc->prelude.opcodes - (const byte *)iter->rc->fun_data);
mp_uint_t bc_end = iter->rc->fun_data_len - prelude_size;
if (iter->bc >= bc_end) {
return MP_OBJ_STOP_ITERATION;
} else {
iter->bc = bc_end;
}
}

mp_uint_t end = iter->bc;
mp_obj_t next[3] = {MP_OBJ_NEW_SMALL_INT(start), MP_OBJ_NEW_SMALL_INT(end), MP_OBJ_NEW_SMALL_INT(line_no)};

return mp_obj_new_tuple(MP_ARRAY_SIZE(next), next);
}

static void code_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
if (dest[0] != MP_OBJ_NULL) {
// not load attribute
Expand Down Expand Up @@ -143,6 +204,10 @@ static void code_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
}
dest[0] = o->lnotab;
break;
case MP_QSTR_co_lines:
dest[0] = MP_OBJ_FROM_PTR(&code_colines_obj);
dest[1] = self_in;
break;
}
}

Expand Down
14 changes: 3 additions & 11 deletions py/showbc.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,9 @@ void mp_bytecode_print(const mp_print_t *print, const mp_raw_code_t *rc, size_t
mp_uint_t source_line = 1;
mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line);
for (const byte *ci = code_info; ci < line_info_top;) {
if ((ci[0] & 0x80) == 0) {
// 0b0LLBBBBB encoding
bc += ci[0] & 0x1f;
source_line += ci[0] >> 5;
ci += 1;
} else {
// 0b1LLLBBBB 0bLLLLLLLL encoding (l's LSB in second byte)
bc += ci[0] & 0xf;
source_line += ((ci[0] << 4) & 0x700) | ci[1];
ci += 2;
}
mp_code_lineinfo_t decoded = mp_bytecode_decode_lineinfo(&ci);
bc += decoded.bc_increment;
source_line += decoded.line_increment;
mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line);
}
}
Expand Down
81 changes: 81 additions & 0 deletions tests/basics/fun_code_colines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Check that we have sensical bytecode offsets in function.__code__.co_lines

def f1(x, y, obj, obj2, obj3):
a = x + y # line 4: bc+4 line+4
b = x - y # line 5: bc+4 line+1
# line 6
# line 7
# line 8
# line 9
# line 10
# line 11
# line 12
# line 13
# line 14
# line 15
# line 16
# line 17
# line 18
# line 19
c = a * b # line 20: bc+4 line+15
obj.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.fun() # line 21: bc+31 line+1; bc+27 line+0
# line 22
# line 23
# line 24: bc+0 line+3
# line 25
# line 26
# line 27: bc+0 line+3
# line 28
# line 29
obj2.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.fun() # line 30: bc+31 line+3; bc+27 line+0
# line 31
# line 32
# line 33: bc+0 line+3
# line 34
# line 35
# line 36
# line 37
# line 38
# line 39
# line 40
# line 41
# line 42
# line 43
# line 44
# line 45
# line 46
# line 47
# line 48
# line 49
# line 50
# line 51
# line 52
# line 53
# line 54
# line 55
# line 56
# line 57
# line 58
# line 59
return obj3.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.fun() # line 60: bc+31 line+27; bc+27 line+0

def f2(x, y):
a = x + y # line 63
b = x - y # line 64
return a * b # line 65

try:
f1.__code__.co_lines
except AttributeError:
print("SKIP")
raise SystemExit

print("f1")
for start, end, line_no in f1.__code__.co_lines():
print("line {} start: {}".format(line_no, start))
print("line {} end: {}".format(line_no, end))

print("f2")
for start, end, line_no in f2.__code__.co_lines():
print("line {} start: {}".format(line_no, start))
print("line {} end: {}".format(line_no, end))
20 changes: 20 additions & 0 deletions tests/basics/fun_code_colines.py.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
f1
line 4 start: 0
line 4 end: 4
line 5 start: 4
line 5 end: 8
line 20 start: 8
line 20 end: 12
line 21 start: 12
line 21 end: 70
line 30 start: 70
line 30 end: 128
line 60 start: 128
line 60 end: 186
f2
line 63 start: 0
line 63 end: 4
line 64 start: 4
line 64 end: 8
line 65 start: 8
line 65 end: 12
47 changes: 47 additions & 0 deletions tests/basics/fun_code_full.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Test function.__code__ attributes not available with MICROPY_PY_BUILTINS_CODE <= MICROPY_PY_BUILTINS_CODE_BASIC

try:
(lambda: 0).__code__.co_code
except AttributeError:
print("SKIP")
raise SystemExit

try:
import warnings
warnings.simplefilter("ignore") # ignore deprecation warning about co_lnotab
except ImportError:
pass

def f(x, y):
a = x + y
b = x - y
return a * b

code = f.__code__

print(type(code.co_code)) # both bytes (but mpy and cpy have different instruction sets)
print(code.co_consts) # (not necessarily the same set, but in this function they are)
print(code.co_filename.rsplit('/')[-1]) # same terminal filename but might be different paths on other ports
print(type(code.co_firstlineno)) # both ints (but mpy points to first line inside, cpy points to declaration)
print(code.co_name)
print(iter(code.co_names) is not None) # both iterable (but mpy returns dict with names as keys, cpy only the names; and not necessarily the same set)
print(type(code.co_lnotab)) # both bytes

co_lines = code.co_lines()

l = list(co_lines)
first_start = l[0][0]
last_end = l[-1][1]
print(first_start) # co_lines should start at the start of the bytecode
print(len(code.co_code) - last_end) # and end at the end of the bytecode

prev_end = 0
for start, end, line_no in l:
if end != prev_end:
print("non-contiguous")
break # the offset ranges should be contiguous
prev_end = end
else:
print("contiguous")


Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy