Compilation 浏览「;“发件人”;in Finder还返回不包含';t包含消息I';我在法罗寻找什么?

Compilation 浏览「;“发件人”;in Finder还返回不包含';t包含消息I';我在法罗寻找什么?,compilation,smalltalk,pharo,Compilation,Smalltalk,Pharo,打开新的Pharo 5图像,打开Finder并搜索+选择器。然后选择+并单击发件人。得到了大约5000个结果,其中大多数看起来不错,但是经过仔细检查,有些结果似乎遗漏了我发送的消息的任何参考 我要补充的不是几个,其中大约1700个似乎是错误的,并且没有提及任何这样的内容。你认为问题出在哪里?这里有一个例子: 更新 搜索通过文本源代码和字节码进行;因此它向您显示了该方法,因为它在字节码中找到了#+,但浏览器太有限,无法同时显示文本和字节,因此它只显示文本(即使匹配是在字节码上完成的) 至于字节码本

打开新的Pharo 5图像,打开Finder并搜索+选择器。然后选择+并单击发件人。得到了大约5000个结果,其中大多数看起来不错,但是经过仔细检查,有些结果似乎遗漏了我发送的消息的任何参考

我要补充的不是几个,其中大约1700个似乎是错误的,并且没有提及任何这样的内容。你认为问题出在哪里?这里有一个例子:

更新 搜索通过文本源代码和字节码进行;因此它向您显示了该方法,因为它在字节码中找到了
#+
,但浏览器太有限,无法同时显示文本和字节,因此它只显示文本(即使匹配是在字节码上完成的)

至于字节码本身,Pharo(re)每次保存时都会编译一个方法;例如,当您保存以下方法时

Something>>loop
    1 to: 10 do: [ :each | ]
系统将编译它,当您检查该方法并查看字节码时,您将看到

我相信你也可以手工编写字节码


原始答案 因为对于一些特殊的选择器,它也会查看字节码,当您检查方法本身时可以看到字节码

在不到一分钟的时间内,通过查看Pharo本身的代码可以很容易地发现这一点(一旦您对Pharo更熟悉):

  • #+发送者
    提供选择器的发送者列表
  • 因此,您可以查看
    发件人的实现,并浏览可用的调用链(您可以使用鼠标或双击并按住ctrl+n键选择选择器,以显示发件人浏览器)
    
    • 发件人
      → <代码>所有发件人类别:
    → <代码>选民所指的全部内容:
  • 你看

  • 只是想澄清一下@Peter的回答:

    迭代

      1 to: 10 do: [:each | dictionary at: each put: each]
    
    每次迭代块时,向块变量
    发送
    +1

    我们在源代码中没有看到
    +1
    ,但它实际上发生在幕后。之所以会发现这样一个隐藏的发送,是因为Smalltalk不分析源代码,而是分析
    CompiledMethod
    ,而是通过查看文本帧和字节码(正如Peter所说)来分析源代码

    例如,通过编译和检查方法

    m
      1 to: 10 do: [:i | i foo]
    
    我们可以看到中间表示Ir选项卡的内容如下:

     1. label: 1
     2. pushLiteral: 1
     3. popIntoTemp: #i
     4. goto: 2
    
     5. label: 2
     6. pushTemp: #i
     7. pushLiteral: 10
     8. send: #'<='
     9. if: false goto: 4 else: 3
    
    10. label: 3
    11. pushTemp: #i
    12. send: #foo
    13. popTop
    14. pushTemp: #i
    15. pushLiteral: 1
    16. send: #+                       "Here we sum 1 to i"
    17. popIntoTemp: #i
    18. goto: 2
    
    19. label: 4
    20. returnReceiver
    
    您的方法有4次发送:
    foo
    发送到:do:
    foo
    (再次)和
    。但是,编译器将只生成3次发送
    foo
    foo
    bar
    ,并将
    to:do:
    的字节码包含在适当的位置

    假设
    to:do:
    需要对块参数
    i
    求和
    1
    ,则实际发送的
    send+
    会被检测为在方法中发生(因为它确实发生了)

    但是,如果将该方法重写为

    m
      | block |
      block := [:i | i foo].
      self foo.
      1 to: 10 do: block
      self bar
    
    编译器将拒绝内联
    到:do:
    ,并将其作为常规消息发送。因此,您的方法将不再是
    +
    的发送者


    如果未发送to:do:,为什么我的方法是它的发送者?

    这是一个更微妙的问题。正如我们所看到的,当
    to:do:
    内联时,它不会被发送。那么,我的方法如何可能被识别为
    to:do:
    的发送方


    原因是编译器无论如何都会将符号
    #to:do:
    添加到方法的文本框架中。它会这样做,只是为了允许您的方法作为
    to:do:
    的发送者被找到。不管您的方法是否真的将
    发送到:do:
    ,内联技术只是一种优化。在更高的抽象层次上,我们都希望看到我们的方法是
    to:do:
    的“发送者”,因此需要将其添加到文本框架中的“技巧”。

    您的答案是信息性的,但我仍然不明白。看到我上传的图片中的代码,我没有看到任何对+或#+的调用。当我没有实际编写字节码时,+的调用是通过什么机制进入字节码的。仅仅编译就可以做到这一点吗?查看BytecodeEncoder>>#如果:isSpecialLiteralForPush:从本质上看,系统决定在哪里包含此发送:+bytecode?@Niki编译器进行了大量优化,您有时还可以在方法中看到
    杂注(使用Finder并选择“杂注”搜索),这也会触发VM的不同行为,我现在明白了。可能是出于性能原因,在幕后有人这样做。我想我在编码器中找到了发生这种情况的地方。它在做一些“断言”,最后是“流下一个输出:specialSelectorIndex+175”,我不明白这是如何生成发送的:+它背后的逻辑是什么?@Niki-see
    OCASTTranslator>>emitToDo:step:
    ;编译器执行语义分析,并将
    转换为:do:
    调用为特定字节码。您还可以手动执行编译(
    Someclass compile:'myCode
    )并使用调试器跟踪执行情况。我现在明白了为什么会找到并显示它,基本上字节码中有+,但finder看不到字节码,所以您会看到实际上不包含+的文本。smalltalk决定在何处添加o消息,它是如何工作的?太棒了!我现在明白了!
    self foo.
    1 to: 10 do: [:i | i foo].
    self bar
    
    m
      | block |
      block := [:i | i foo].
      self foo.
      1 to: 10 do: block
      self bar