TIP 583: Add Option to Limit Depth of Execution Step Traces for Procs

Login
Bounty program for improvements to Tcl and certain Tcl packages.
Author:         Eric Taylor <[email protected]>
State:          Draft
Type:           Project
Vote:           Pending
Created:        15-Aug-2020
Post-History:
Keywords:       Tcl
Tcl-Version:    8.7

Abstract

This TIP proposes that the trace command include an option for step tracing (of procedures) that would limit the traces to a given depth when using the enterstep or leavestep trace options.

Specification

An optional extra parameter and value,

-levels maxdepth

would be implemented that would take a value of 0, 1, .. N for maxdepth, that would limit the depth of procedure stepping traces to the given value. A value of 0 (the default if the option is not specified) would mean no limit, and would behave the same as it does now. A value of 1 would be useful for avoiding unwanted repeated traces, and other values larger than 1 could be used to limit performance costs.

The option could be added at the end of a trace add execution command.

trace add execution name ops commandPrefix ?-levels maxdepth?

This option would only affect the trace execution ops of enterstep and leavestep.

Rationale and Discussion

The current implementation of step tracing with enterstep and leavestep will step trace commands in called procedures and continue step tracing in all nested procedure calls. This can be inconvenient and also quite costly in performance. One particular problem that occurs when using traces on multiple procedures is that if A calls B and a trace is set on both A and B, then there will be 2 traces triggered for each command in B, one that goes to the command prefix for each of the traces. The following code,

proc A {args} {
B $args
}
proc B {args} {
set foo $args
}
proc tracer {args} {
puts "tracer: $args"
}
trace add execution A enterstep {tracer 1 }
trace add execution B enterstep {tracer 2 }
A something
B somethingmore

will produce the following output:

tracer: 1 {B something} enterstep
tracer: 1 {set foo something} enterstep
tracer: 2 {set foo something} enterstep
tracer: 2 {set foo somethingmore} enterstep

Here we see that the statement [set foo $args] is traced twice. This can be avoided if the trace on B is not included, but then if B is called directly, as in the command [B somethingmore] B will not be step traced. To manage the double tracing, one might use a different command prefix, but this can be both tricky and costly in performance. The more procedures that are traced the more complex and confusing the results can become.

This is mentioned in the documentation which states:

"Note that the creation of many enterstep or leavestep traces can lead to unintuitive results..."

If the proposed -levels option were implemented, and the maximum depth were set to 1, then the above example problem could be eliminated. Then a user could set a trace for any and all procedures and know that each step trace would occur precisely once.

Another concern with the current implementation is that tracing can be very expensive. Even a simple trace function that say, counts commands executed, can run 20-50 times slower. If the procedure B in the above case has a (possibly large) loop, then every iteration will cause a step trace. If the user only wants to step trace procedure A, there is no way currently to avoid also step tracing procedure B. It is possible to use scripting level code to look at [info level] and return from the called command prefix as soon as possible, but at minimum, there will still be a heavy performance cost. And figuring out what level to skip can be difficult, especially if a traced procedure is called from different places and levels in a program, such as being called from both a TK event and also from a procedure. When using an option of -levels 1, a program could set a separate trace for all procedures, and know that each proc's commands would only be traced once. For example, suppose there were 3 procedures A, B, and C:

trace add execution A enterstep {tracer A } -levels 1
trace add execution B enterstep {tracer B } -levels 1
trace add execution C enterstep {tracer C } -levels 1

The option of -levels 1 would insure that tracer was called only once for each statement in the 3 procedures, even if one of the procedures calls another. By also specifying a command prefix as shown above (with an added space after the proc name), it would result in an additional first parameter sent to the tracer proc so it could readily know which proc was being traced. This could significantly simplify coding.

Including a -levels option would also be useful for an interactive debugger based on the execution traces. For example, when stepping through code, it is often useful to have a step into and step over command. Using a -levels 1 option would facilitate a step over capability with ease.

Note that while the above examples reference only enterstep, implementing this option for leavestep should not present any significant difficulties.

Proposal

The tcl trace code would be modified to keep track of the stack level (if this is not already done) and bypass the call to the command prefix if -level N was given and the level is greater than N. If this option is specified for operations other than enterstep and leavestep, the option would have no affect, or could be flagged as an error.

Incompatibilities

No significant incompatibilities should result from using this option, as it would be implemented as an additional command argument, that at present would result in an error for an extra argument. This could conceivably affect some tests for extraneous arguments.

Copyright

This document has been placed in the public domain.