Version 0 of dis2asm learns to catch

Updated 2013-12-01 09:28:46 by suchenwi

Richard Suchenwirth 2013-12-01 - Another chapter in the dis2asm saga. The set of accepted TAL instructions includes beginCatch and endCatch, so I wanted to try that out.

% aproc f x {catch {expr {1/$x}}} -x
proc f x {asm {
   beginCatch               ;# (0) beginCatch4 0
   push 1                   ;# (5) push1 0         # "1"
   load x                   ;# (7) loadScalar1 %v0         # var "x"
   div                      ;# (9) div
   pop                      ;# (10) pop
   push 0                   ;# (11) push1 1         # "0"
   jump L16                 ;# (13) jump1 +3         # pc 16
   pushReturnCode           ;# (15) pushReturnCode
 label L16;
   endCatch                 ;# (16) endCatch
                            ;# (17) done
 label Done;
}}
% f 4
wrong # args: should be "beginCatch label"

Hmm.. but which label should it be? The disassembly contains no evident label (0 would be its own position...). I pasted the generated asm proc into the editor and tried the first evident possibility - there is only a single label L16. But writing it after the beginCatch instruction, and rertesting, brought:

 inconsistent stack depths on two execution paths

Looking closer at the TAL code, there is jump L16 two lines above, and then pushReturnCode which in this state is unreachable. It doesn't have a label, but we can easily assign one.

Apparently, when dis2asm encounters a beginCatch instruction, it must perform a look-ahead from the current position to find a suitable pushReturnCode. Of course catches can be nested (the depth might be indicated by the "0" argument in the disassembly), but as neither the matching pushReturnCode nor endCatch indicate at what nesting depth they are, we can only disallow nested catches for now, and take the first pushReturnCode that comes along. Easy, and as the body of dis2asm is already longer than I like, I wrote a proc for that:

proc findCatchEnd {lines lineno} {
    for {set i $lineno} {$i < [llength $lines]} {incr i} {
        if {[regexp {\((\d+)\) pushReturnCode} [lindex $lines $i] -> pc]} {
            return $pc
        }
    }
    error "could not find end of catch beginning at line $lineno"
}

Its call is placed in the command-specific switch in dis2asm:

...
            switch -- $instr0 {
                beginCatch {
                    set catchend [findCatchEnd $lines $lineno]
                    lappend code L$catchend
                    lappend jumptargets $catchend
                }
                done          {
...

It retrieves the program counter of the pushReturnCode, supplies it to the beginCatch instruction as required, and also puts it on the list of jump targets, so a label pseudo-instruction is inserted there when the time comes.

Testing shows that the generated TAL is now well-formed, and catch reacts as we expect:

% aproc f x {catch {expr {1/$x}}} -x
proc f x {asm {
   beginCatch L15           ;# (0) beginCatch4 0
   push 1                   ;# (5) push1 0         # "1"
   load x                   ;# (7) loadScalar1 %v0         # var "x"
   div                      ;# (9) div
   pop                      ;# (10) pop
   push 0                   ;# (11) push1 1         # "0"
   jump L16                 ;# (13) jump1 +3         # pc 16
 label L15;
   pushReturnCode           ;# (15) pushReturnCode
 label L16;
   endCatch                 ;# (16) endCatch
                            ;# (17) done
 label Done;
}}
% f 1
0
% f x
1