Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import textwrap
import subprocess
import warnings

try:
import _testinternalcapi
except ImportError:
Expand Down Expand Up @@ -360,6 +361,8 @@ class TraceTestCase(unittest.TestCase):
# Disable gc collection when tracing, otherwise the
# deallocators may be traced as well.
def setUp(self):
if os.environ.get('PYTHON_UOPS_OPTIMIZE') == '0':
self.skipTest("Line tracing behavior differs when JIT optimizer is disabled")
self.using_gc = gc.isenabled()
gc.disable()
self.addCleanup(sys.settrace, sys.gettrace())
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ def test_traced_func_linear(self):

self.assertEqual(self.tracer.results().counts, expected)

@unittest.skipIf(os.environ.get('PYTHON_UOPS_OPTIMIZE') == '0',
"Line counts differ when JIT optimizer is disabled")
def test_traced_func_loop(self):
self.tracer.runfunc(traced_func_loop, 2, 3)

Expand All @@ -166,6 +168,8 @@ def test_traced_func_importing(self):

self.assertEqual(self.tracer.results().counts, expected)

@unittest.skipIf(os.environ.get('PYTHON_UOPS_OPTIMIZE') == '0',
"Line counts differ when JIT optimizer is disabled")
def test_trace_func_generator(self):
self.tracer.runfunc(traced_func_calling_generator)

Expand Down Expand Up @@ -236,6 +240,8 @@ def setUp(self):
self.my_py_filename = fix_ext_py(__file__)
self.addCleanup(sys.settrace, sys.gettrace())

@unittest.skipIf(os.environ.get('PYTHON_UOPS_OPTIMIZE') == '0',
"Line counts differ when JIT optimizer is disabled")
def test_exec_counts(self):
self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0)
code = r'''traced_func_loop(2, 5)'''
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix JIT trace buffer overrun by pre-reserving exit stub space. Patch By
Donghee Na.
24 changes: 10 additions & 14 deletions Python/optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ add_to_trace(
assert(func == NULL || func->func_code == (PyObject *)code); \
instr = trace_stack[trace_stack_depth].instr;


/* Returns the length of the trace on success,
* 0 if it failed to produce a worthwhile trace,
* and -1 on an error.
Expand All @@ -560,8 +561,10 @@ translate_bytecode_to_trace(
_Py_BloomFilter_Add(dependencies, initial_code);
_Py_CODEUNIT *initial_instr = instr;
int trace_length = 0;
// Leave space for possible trailing _EXIT_TRACE
int max_length = buffer_size-2;
// Leave space for possible trailing _EXIT_TRACE and estimated exit stubs
// Reserve 20% of buffer space for exit stubs (empirically sufficient)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a tighter and more logical bound than this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe 15%? or 10%?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10% is too small. Let me test from 15%.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I mean something based on math. Like a loose bound would be:

Every instruction needs a _DEOPT or _EXIT_TRACE

_DEOPT might have _ERROR_POP_N before it

Therefore from n instructions we must reserve 2n instructions.

This bound is overly loose, but it would be good to work it out

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, let me think about this differently. I’ll share the next commit with an improvement that tracks each instruction based on a mathematical assumption

int max_exit_stubs = (buffer_size * 20) / 100; // 20% for exit stubs
int max_length = buffer_size - 2 - max_exit_stubs;
struct {
PyFunctionObject *func;
PyCodeObject *code;
Expand Down Expand Up @@ -647,16 +650,7 @@ translate_bytecode_to_trace(
assert(!OPCODE_HAS_DEOPT(opcode));
}

if (OPCODE_HAS_EXIT(opcode)) {
// Make space for side exit and final _EXIT_TRACE:
RESERVE_RAW(2, "_EXIT_TRACE");
max_length--;
}
if (OPCODE_HAS_ERROR(opcode)) {
// Make space for error stub and final _EXIT_TRACE:
RESERVE_RAW(2, "_ERROR_POP_N");
max_length--;
}
// Note: Exit stub space is pre-reserved in max_length calculation above
switch (opcode) {
case POP_JUMP_IF_NONE:
case POP_JUMP_IF_NOT_NONE:
Expand Down Expand Up @@ -731,9 +725,11 @@ translate_bytecode_to_trace(
{
const struct opcode_macro_expansion *expansion = &_PyOpcode_macro_expansion[opcode];
if (expansion->nuops > 0) {
// Reserve space for nuops (+ _SET_IP + _EXIT_TRACE)
// Reserve space for nuops
int nuops = expansion->nuops;
RESERVE(nuops + 1); /* One extra for exit */

// Reserve space for nuops (exit stub space already pre-reserved)
RESERVE(nuops);
int16_t last_op = expansion->uops[nuops-1].uop;
if (last_op == _RETURN_VALUE || last_op == _RETURN_GENERATOR || last_op == _YIELD_VALUE) {
// Check for trace stack underflow now:
Expand Down
Loading