Update: the easiest method to deal with it: in the action, raise InterpreterException?,"message". In the calling code, detect that and abort the interpreter with that message.

Interpreter Watchpoints, and Run-from-Line

I recently laid out some thoughts about line number handling in the
interpreter, which - among many other uses - relates to the Run-From-Line feature (see
http://wiki.linuxcnc.org/cgi-bin/wiki.pl?LineNumbers). While I have
not implemented the proposal outlined there, I've made some progress
on a different approach to the Run-From-Line theme.

The mechanism - interpreter watchpoints - can be used for RFL, but is
much more generally applicable than that.

Restating the problem

The current RFL implementation works roughly like this (I'm skipping
some details here like boundary tests):

task records the fact that RFL for line > 1 was issued

task starts the interpreter on the current file

the interpreter generates canon commands and puts them onto the interplist, along with the current line number in each interplist node.

task inspects the nodes in turn, and sees whether the linenumber in the current node is greater equal to the RFL line.

Viewed a bit differently, task is looking for a start condition, and
does so by inspecting the line numbers. Consequently, the line number
- with all its deficiencies - is the *only* start condition which we
have.

Moving evalution of conditions to the interpreter: introducing watchpoints

Task clearly isnt a great place for ex-post analysis of a condition
which the interpreter could have determined much better in the first
place, and it's kind of too late at that point to get clever about it.

We can do the same with the RS274NGC interpreter, in particular since
the embedded Python interpreter gives pretty much full access to
internal state.

The overall idea is as follows:

provide methods to define a set of watchpoints, which are Python expressions

execute them before each block is executed (the "stepping" in gdb)

record the watchpoint results and pass them along with the canon commands generated.

An example Interpreter method could look like so:

interp.set_watchpoint(0, "this.sequence_number >= 7")

Without going through the details: the expression would be true after
line 7 of the NGC program has been reached.

Now, if task could in turn inspect the results of this watchpoint,
this would not only give the equivalent for Run-from-line, but the
capability to associate arbitrary expressions as a start condition.

proof-of-concept

introduce some new interpreter methods to add and manipulate watchpoints

set_watchpoint(int num, const char *expression)

print_watchpoints()

execute_watchpoints()

add a canon method to convey the current state of watchpoints (currently up to 32): WATCHPOINT_UPDATE(unsigned condition)

add methods to interpl.hh/cc to accept and convey the condition mask

add rudimentary support in task to inspect the current command's condition mask as a run-from-line replacement.

This emulates the current RFL feature by constructing a trivial
watchpoing ("this.sequence_number >= startline") and watching the
conditon.

To see how it works, build this branch, turn on debugging to
0xFFFFFFFF and do a run-from-line of an arbitrary NGC program. You
should see the WATCHPOINT_UPDATE changes and the moves only starting
after the given line.

The watchpoint mask might change often; the Run-From-Line condition is derived as ' execute commands after watchpoint 0 has been found true for the first time'.

Python bindings

The new methods are exposed to Python internally and can be used through the ';py,...python code...' pseudocomment feature, see tests/interp/watchpoints .

Other uses

Watchpoints are not limited to emulating run-from-line. Some other
uses could be:

more complex start conditions (up to the outright whacky), like: "run from line X in procedure Y but only if parameter Z has value foo", or "start after third recursion of procedure X"

assertions, for instance arbitrary axis/joint boundary tests: "assert that the controlled point fulfills condition X", where X is some arbitrary test of a geometric property defined in Python.

stop conditions: RFL could easily be supplemented by a Run-To-line.

breakpoints: doable, but pending better support of multiple interpreter contexts in Linuxcnc

I havent fully thought through the implications for stepping, and
motion for that matter. I think some use can be made, but for that I
have to first understand the current stepping code better.

The current code also permits non-expression watchpoints like so:

pythonprocedure()

or

if <expression>: action()

to call arbitrary Python code (eg a boundary test). However, there is
currently no action associated with such a statement (like aborting
the interpreter).

Update: the easiest method to deal with it: in the action, raise InterpreterException?,"message". In the calling code, detect that and abort the interpreter with that message.

Example for a complex start condition

The watchpoint in the following example reads as:

start executing when in Oword subroutine 'mysub' AND

the named parameter '#<val>' exists AND

#<val> is < 2

;load and point on the next line in Axis, then RFL - this will override the default run-from-line watchpoint
;py,this.set_watchpoint(0,"this.sub_context[this.call_level].subname == 'mysub' and this.params.exists('val') and this.params['val'] < 3")

which I read as: when stepping, motion is looking for a change in idForStep? (which is the line_number). Since only inequality is used, I conjecture it would be enough to have a single bit which changes on each line. A different bit in the condition mask could fulfill this purpose, and a watchpoint which changes this bit on every new line encountered. Sounds right ?

Update: this doesnt work - there seems to be an assumption about increasing motion id's, maybe in the tp.

Motion: Passing the watchpoint condition mask down to motion along
with the linenumber is easy. Not sure about the use scenario.

Task: there's no reason why task couldnt have such watchpoints as
well, although I dont see a useful scenario at this point (safety?). The
footwork is mostly done - much of task and interpreter internals are
exposed to Python, and doing it should be easy - if one comes up with
a good use case.