如何执行完全字节码编译?

时间:2019-04-26 18:10:43

标签: tcl

我正在使用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命令的编译功能。

1 个答案:

答案 0 :(得分:1)

Tcl将脚本或过程的编译推迟到第一次需要该脚本/过程的字节编码版本时。编译是相当快的(在有意义的地方仔细地缓存),并且8.6中的优化器是轻量级的(只是杀死了以前生成的一些愚蠢的代码序列),所以通常这不是一个大问题。为特定命令完成的编译程度相差很大: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"
    }
}}}

认为 getbytecodedisassemble快一点(它做同样的事情,但是产生机器可读的输出),但是我可能错了。如果要在8.5中使用该代码,则需要使用disassemble