Richard Suchenwirth 2013-11-30 - Another chapter in the dis2asm saga: a converter from the language produced by the tcl::unsupported::disassemble command (briefly "dis"), to the language expected by the tcl::unsupported::assemble command, also known as TAL (Tcl Assembly Language). Both are varieties of assembler for Tcl's bytecode engine, but there are also a number of variations between them, which dis2asm tries to fix.
One remaining issue on the dis2asm page was that a "done" instruction (with a semantics like return, but not available in TAL, so I just skipped it so far) produced bad code if not at its end. Example (the "done" in question is at position (9)):
% aproc f x {if {$x > 0} {return 1} else {return 0}} -x proc f x {asm { load x ;# (0) loadScalar1 %v0 # var "x" push 0 ;# (2) push1 0 # "0" gt ;# (4) gt jumpFalse L12 ;# (5) jumpFalse1 +7 # pc 12 push 1 ;# (7) push1 1 # "1" ;# (9) done ;# (10) nop ;# (11) nop label L12; push 0 ;# (12) push1 0 # "0" ;# (14) done ;# (15) done }} % f 2 inconsistent stack depths on two execution paths
I had already proposed a fix for this on dis2asm: convert "done" to "jump Done" when not at or near the end of the input, and add a "label Done" at the very end of the output. The TAL output now runs well, and looks like
% aproc f x {if {$x > 0} {return 1} else {return 0}} -x proc f x {asm { load x ;# (0) loadScalar1 %v0 # var "x" push 0 ;# (2) push1 0 # "0" gt ;# (4) gt jumpFalse L12 ;# (5) jumpFalse1 +7 # pc 12 push 1 ;# (7) push1 1 # "1" jump Done ;# (9) done ;# (10) nop ;# (11) nop label L12; push 0 ;# (12) push1 0 # "0" ;# (14) done ;# (15) done label Done; }} % f 42 1 % f -42 0
So here is the latest version of dis2asm that gets things done. I had to add a line counter, so we know where in the input we are, and also made the output format a little tidier.
proc dis2asm body { set fstart " push -1; store @p; pop " set fstep " incrImm @p +1;load @l;load @p listIndex;store @i;pop load @l;listLength;lt " set res "" set wait "" set jumptargets {} set lines [split $body \n] foreach line $lines { ;#-- pass 1: collect jump targets if [regexp {\# pc (\d+)} $line -> pc] {lappend jumptargets $pc} } set lineno 0 foreach line $lines { ;#-- pass 2: do the rest incr lineno set line [string trim $line] if {$line eq ""} continue set code "" if {[regexp {slot (\d+), (.+)} $line -> number descr]} { set slot($number) $descr } elseif {[regexp {data=.+loop=%v(\d+)} $line -> ptr]} { #got ptr, carry on } elseif {[regexp {it%v(\d+).+\[%v(\d+)\]} $line -> copy number]} { set loopvar [lindex $slot($number) end] if {$wait ne ""} { set map [list @p $ptr @i $loopvar @l $copy] set code [string map $map $fstart] append res "\n $code ;# $wait" set wait "" } } elseif {[regexp {^ *\((\d+)\) (.+)} $line -> pc instr]} { if {$pc in $jumptargets} {append res "\n label L$pc;"} if {[regexp {(.+)#(.+)} $instr -> instr comment]} { set arg [list [lindex $comment end]] if [string match jump* $instr] {set arg L$arg} } else {set arg ""} set instr0 [normalize [lindex $instr 0]] switch -- $instr0 { concat - invokeStk {set arg [lindex $instr end]} incrImm {set arg [list $arg [lindex $instr end]]} } set code "$instr0 $arg" switch -- $instr0 { done { if {$lineno < [llength $lines]-2} { set code "jump Done" } else {set code ""} } startCommand {set code ""} foreach_start {set wait $line; continue} foreach_step {set code [string map $map $fstep]} } append res "\n [format %-24s $code] ;# $line" } } append res "\n label Done;\n" return $res }