【问题标题】:When is a command's compile function called?什么时候调用命令的编译函数?
【发布时间】:2016-12-24 01:27:03
【问题描述】:

我对TCL执行的理解是,如果定义了一个命令的编译函数,那么在调用它的执行函数之前,它会在执行该命令时首先被调用。

以命令 append 为例,在 tclBasic.c 中定义如下:

static CONST CmdInfo builtInCmds[] = {

    {"append",          (Tcl_CmdProc *) NULL,   Tcl_AppendObjCmd,
        TclCompileAppendCmd,            1},

这是我的测试脚本:

$ cat t.tcl
set l [list 1 2 3]
append l 4

我在 TclCompileAppendCmd 和 Tcl_AppendObjCmd 这两个函数中添加了 gdb 断点。我的期望是在 Tcl_AppendObjCmd 之前命中 TclCompileAppendCmd。

Gdb 的目标是 tclsh8.4,参数是 t.tcl。

我看到的很有趣:

  1. TclCompileAppendCmd 确实首先被命中,但它不是来自 t.tcl, 而是来自 init.tcl。
  2. TclCompileAppendCmd 被多次命中,它们都来自 init.tcl。
  3. t.tcl 第一次执行,是 Tcl_AppendObjCmd 被命中,而不是 TclCompileAppendCmd。

我无法理解它:

  1. 为什么为 init.tcl 调用编译函数而不为 t.tcl 调用编译函数?

  2. 每个脚本都应该独立编译,即在 init.tcl 处附加编译命令的对象不会被以后的脚本重用,不是吗?

[更新] 感谢Brad 的提示,在我将脚本移动到 proc 后,我可以看到 TclCompileAppendCmd 被命中。

【问题讨论】:

  • 尝试将您的测试放入 proc 中。 Q2 有某种错误的假设。 append 命令已编译。
  • 这是 Tcl 字节码编译器的怪癖之一。一般来说,一旦脚本开始变长,创建一个主过程并将所有内容放入其中以便编译。
  • EDA 工具中的非常大的脚本除外。那些遇到了一些可怕的内存限制......

标签: tcl


【解决方案1】:

编译函数(在您的示例中为TclCompileAppendCmd)在字节码编译器想要为该特定命令的特定实例发出字节码时调用。如果一个命令没有编译函数,字节码编译器也有一个回退:它发出指令来调用标准实现(在这种情况下是Tcl_AppendObjCmd;另一个字段中的NULL导致Tcl生成一个thunk如果有人真的坚持使用特定的 API,但你可以忽略它)。这是一个有用的行为,因为它是处理 I/O 等操作的方式;与执行磁盘或网络 I/O 的开销相比,调用标准命令实现的开销非常小。

但是字节码编译器什么时候运行呢?

在一个级别上,只要 Tcl 的其余部分要求它运行,它就会运行。简单的!但这对你并没有真正的帮助。更重要的是,只要 Tcl 评估 Tcl_Obj 中还没有字节码类型的脚本值(或者如果保存的字节码表明它用于不同的分辨率上下文或不同的编译时期),它就会运行 除非评估要求不通过标志TCL_EVAL_DIRECTTcl_EvalObjExTcl_EvalEx(这是Tcl_EvalObjEx 的方便包装器)来编译字节码。正是那个标志给你带来了问题。

该标志何时使用?

它实际上非常简单:当认为某些代码将只运行一次时使用它,因为编译成本大于使用解释路径的成​​本。它特别被 Tk 的bind 命令用于运行替代脚本回调,但它也被sourcetclsh 的主要代码使用(基本上任何使用Tcl_FSEvalFileEx 或其前身/包装器Tcl_FSEvalFileTcl_EvalFile)。我不能 100% 确定这是否是 sourced 上下文的正确选择,但它就是现在发生的情况。但是,如果您正在处理循环,则有一个(非常!)值得的解决方法:您可以使用立即调用的过程或使用 apply 将代码放在 source 内的编译上下文中(我建议后者这些天)。 init.tcl 使用了这些技巧,这就是你看到它编译东西的原因。

不,我们通常不会在 Tcl 运行之间保存已编译的脚本。我们的编译器足够快,这并不值得;验证加载的编译代码对于当前解释器是否正确的成本足够高,以至于从源代码重新编译实际上更快。我们当前的编译器是快速(我正在研究一种速度较慢的编译器,它可以生成更好的代码)。 ActiveState 有一个商业工具套件(Tcl Dev Kit),其中包括一个提前编译器,但它的重点是为商业部署而不是速度目的隐藏代码。

【讨论】:

  • 我的回答严格适用于 8.5 和 8.6,但可以很好地转移到 8.4
猜你喜欢
  • 2013-02-24
  • 1970-01-01
  • 1970-01-01
  • 2019-09-04
  • 2011-12-31
  • 2012-05-14
  • 2019-04-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多