Richard Suchenwirth 2013-12-03 - This page contains the file tal.tcl with my experiments on the Tcl Assembly Language TAL, and specifically the dis2asm converter and the TAL optimizer. It is included by Visual TAL. For convenience, I'll paste the file here after any non-trivial updates, but will not comment much.
#!/usr/bin/env tclkit package require Tcl 8.6 # tal.tcl -- experiments with the Tcl Assembly Language namespace path ::tcl::mathop #interp alias {} asm {} ::tcl::unsupported::assemble ;# worksn't if {[info commands asm] eq ""} { namespace eval tcl::unsupported {namespace export assemble} namespace import tcl::unsupported::assemble rename assemble asm interp alias {} disasm {} ::tcl::unsupported::disassemble interp alias {} repr {} ::tcl::unsupported::representation ;# unrelated, useful } proc aproc {name argl body args} { proc $name $argl $body set res [disasm proc $name] if {"-x" in $args} { set res [list proc $name $argl [list asm [dis2asm $res]]] if {"-o" in $args} {set res [optimize $res]} eval $res } return $res } proc dis2asm body { global slot array unset slot set fstart " push -1\n store @p\n pop" set fstep "incrImm @p +1\n load @l\n load @p listIndex\n store @i\n pop load @l\n listLength\n 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 set needDone 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 - listIndexImm - reverse { set arg [lindex $instr end] } incrImm {set arg [list $arg [lindex $instr end]]} } set code "$instr0 $arg" switch -- $instr0 { beginCatch { set catchend [findCatchEnd $lines $lineno] lappend code L$catchend lappend jumptargets $catchend } done { if {$lineno < [llength $lines]-2} { set code "jump Done" incr needDone } 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" } } if $needDone {append res "\n label Done;"} return $res\n } proc normalize instr { regsub {\d+$} $instr "" instr ;# strip off trailing length indicator set instr [string map { existScalar exist #incr1Imm incrImm incrScalar1Imm incrImm incrScalar incr lappendScalar lappend loadScalar load nop "" storeScalar store unsetScalar unset } $instr] return $instr } proc optimize tal { set last "" set last2 "" set lines [split $tal \n] for {set i 0} {$i < [llength $lines]} {incr i} { set instr [regexp -inline {[A-Za-z0-9_]+} [lindex $lines $i]] if {"$last $instr" eq "push pop"} { lset lines $i-1 #o[lindex $lines $i-1] ;# mark for deletion lset lines $i #o[lindex $lines $i] } elseif {"$last $instr" eq "jump jump"} { ;# unreachable jump lset lines $i #o[lindex $lines $i] } set last $instr } while 1 { set tmp {} ;# remove marked lines foreach line $lines {if ![string match #o* $line] {lappend tmp $line}} set lines $tmp set found 0 for {set i 0} {$i < [llength $lines]} {incr i} { set instr [regexp -inline {[A-Za-z0-9_]+} [lindex $lines $i]] if {"$last2 $last $instr" eq "jump label jump"} { set oldTrg [string trimright [lindex $lines $i-1 1] ";"] set newTrg [lindex $lines $i 1] lset lines $i-1 #o[lindex $lines $i-1] ;# mark for deletion lset lines $i #o[lindex $lines $i] set found 1 break } set last2 $last set last $instr } if $found { set tmp {} ;# remove marked lines foreach line $lines { if [regexp "jump.* $oldTrg " $line] { set line [string map [list " $oldTrg " " $newTrg "] $line] } if ![string match #o* $line] {lappend tmp $line} } set lines $tmp } else break } return [join $lines \n] } proc findCatchEnd {lines lineno} { set pc "" for {set i $lineno} {$i < [llength $lines]} {incr i} { if {[regexp {\((\d+)\) endCatch} [lindex $lines $i] -> pc]} break } if {$pc eq ""} {error "could not find end of catch for line $lineno"} for {set i $lineno} {$i < [llength $lines]} {incr i} { if {[string match "*jump*pc $pc*" [lindex $lines $i]]} { if {[regexp {\((\d+)\)} [lindex $lines $i+1] -> pc2]} { return $pc2 } } } error "could not find jump source for $pc" } set masm_subs {"push $" "load " jumpgt "gt;jumpTrue"} ;#------------------------------------ small experiments with meta-assembly proc masm code {uplevel 1 [list asm [string map $::masm_subs $code]]} proc mgt {x y} {masm { load x;load y;jumpgt GT;push no;jump Done; label GT; push yes label Done }} #--------------------------------------------------------------------------------------------- TEST SUITE foreach i {total failed} {set $i 0} proc test {cmd -> expected} { incr ::total catch {uplevel 1 $cmd} res if {$res ne $expected} { puts "***** $cmd -> $res, expected $expected" incr ::failed } } test {aproc f x {incr x}; f 41} -> 42 test {aproc f x {incr x} -x; f 22} -> 23 test {aproc f x {incr x} -x -o; f 522} -> 523 test {aproc f x {incr x -1} -x -o; f 522} -> 521 test {aproc f x {set a($x) 1; array get a}; f foo} -> {foo 1} test {aproc f x {set a(1) $x; array get a}; f foo} -> {1 foo} test {aproc sum x {expr [join $x +]} -x; sum {3 4 5}} -> 12 test {asm {expr sqrt(9)}} -> 3.0 proc lempty lst {masm {push $lst;listLength;push 0;eq}} test {lempty {}} -> 1 test {lempty a} -> 0 test {mgt 1 2} -> no test {mgt 4 3} -> yes puts "total $total tests, failed $failed"