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

问题描述 投票:0回答:1

我使用的是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
1个回答
1
投票

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"
    }
}}}

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

© www.soinside.com 2019 - 2024. All rights reserved.