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"