我使用的是TCL8.6.8。
这是我的实验:
>cat ~/tmp/1.tcl
proc p {} {
foreach i {a b c} {
if {$i == "b"} {
break
}
puts $i
}
}
现在我进入tclsh:
% proc disa {file_name} {
set f [open $file_name r]
set data [read -nonewline $f]
close $f
tcl::unsupported::disassemble script $data
}
% disa ~/tmp/1.tcl
ByteCode 0x0x55cabfc393b0, refCt 1, epoch 17, interp 0x0x55cabfbdd990 (epoch 17)
Source "proc p {} {\nforeach i {a b c} {\n if {$i == \"b\"} ..."
Cmds 1, src 175, inst 11, litObjs 4, aux 0, stkDepth 4, code/src 1.26
Code 220 = header 168+inst 11+litObj 32+exc 0+aux 0+cmdMap 4
Commands 1:
1: pc 0-9, src 0-87
Command 1: "proc p {} {\nforeach i {a b c} {\n if {$i == \"b\"} ..."
(0) push1 0 # "proc"
(2) push1 1 # "p"
(4) push1 2 # ""
(6) push1 3 # "\nforeach i {a b c} {\n if {$i == \"b..."
(8) invokeStk1 4
(10) done
您可以看到它没有完全编译为字节码,因为foreach的嵌套脚本被视为文字字符串。
现在我使用tcl::unsupported::disassemble proc
而不是tcl::unsupported::disassemble script
,我可以得到一个完整的字节码编译版本:
% source ~/tmp/1.tcl
% tcl::unsupported::disassemble proc p
ByteCode 0x0x55cabfc393b0, refCt 1, epoch 17, interp 0x0x55cabfbdd990 (epoch 17)
Source "\nforeach i {a b c} {\n if {$i == \"b\"} {\n ..."
File "/home/jibin/tmp/1.tcl" Line 1
Cmds 4, src 76, inst 54, litObjs 4, aux 1, stkDepth 5, code/src 4.21
Code 320 = header 168+inst 54+litObj 32+exc 28+aux 16+cmdMap 16
Proc 0x0x55cabfc72820, refCt 1, args 0, compiled locals 1
slot 0, scalar, "i"
Exception ranges 1, depth 1:
0: level 0, loop, pc 7-47, continue 49, break 50
Commands 4:
1: pc 0-52, src 1-74 2: pc 7-41, src 25-60
3: pc 23-36, src 50-54 4: pc 42-47, src 66-72
Command 1: "foreach i {a b c} {\n if {$i == \"b\"} {\n br..."
(0) push1 0 # "a b c"
(2) foreach_start 0
[jumpOffset=-42, vars=[%v0]]
Command 2: "if {$i == \"b\"} {\n break\n ..."
(7) startCommand +34 1 # next cmd at pc 41, 1 cmds start here
(16) loadScalar1 %v0 # var "i"
(18) push1 1 # "b"
(20) eq
(21) jumpFalse1 +18 # pc 39
Command 3: "break..."
(23) startCommand +14 1 # next cmd at pc 37, 1 cmds start here
(32) jump4 +18 # pc 50
(37) jump1 +4 # pc 41
(39) push1 2 # ""
(41) pop
Command 4: "puts $i..."
(42) push1 3 # "puts"
(44) loadScalar1 %v0 # var "i"
(46) invokeStk1 2
(48) pop
(49) foreach_step
(50) foreach_end
(51) push1 2 # ""
(53) done
这是我的问题:为什么tcl::unsupported::disassemble script
没有完全编译脚本? foreach
命令在proc中,我想proc
的编译函数会调用每个命令的编译功能,因此无论如何都会调用foreach
命令的编译功能。
Tcl推迟编译脚本或过程,直到第一次需要脚本/过程的字节编码版本为止。编译速度相当快(并且在有意义的情况下仔细缓存)并且8.6中的优化器是轻量级的(只是杀死了一些曾经生成的stupider代码序列),所以这通常不是一个大问题。为特定命令完成的编译程度变化很大:expr
几乎总是被深度编译(如果可能!)并且proc
本身从未被编译;你在反汇编中看到的是通用命令调用编译(推送堆栈上的文字,用那么多单词调用泛型命令,完成工作)。这是有道理的,因为proc
的大多数调用只发生过一次,并且只是为了以后发生的有趣事情而真正做好准备。我们改变proc
本身以获得深度编译(与其创建的程序相反)的机会为零,至少为8.7 / 9.0,可能远远超过那里。没有可能证明它需要的工作是合理的。
但是,如果您想尽早触发过程编译,则可以。所需要的只是一点点触发......
trace add execution proc leave {apply {{cmdArgs code result op} {
if {$code == 0} {
# proc succeeded; it must have been called as: proc name args body
set procedureName [lindex $cmdArgs 1]
# Make sure we resolve the procedure name in the right namespace!
set compTime [lindex [time {
uplevel 1 [list tcl::unsupported::getbytecode proc $procedureName]
}] 0]
# We're done now! Totally optional print of how long it took…
puts stderr "Compiled $procedure in $compTime µs"
}
}}}
我认为getbytecode
比disassemble
快一点(它做同样的一般事情,但产生机器可读的输出)但我可能错了。如果要在8.5中使用代码,则需要使用disassemble
。