Просмотр отправителей в Finder также возвращает код, который не содержит сообщения, которое я ищу в Pharo?

Открыл свежее изображение Pharo 5, открыл Finder и поискал селектор +. Затем выберите «+» и нажмите «Отправители». Получил около 5000 результатов, большинство из них кажутся хорошими, однако при ближайшем рассмотрении в некоторых результатах отсутствует какая-либо ссылка на мое сообщение.

Я мог бы добавить немало, около 1700 из них кажутся ошибочными и не содержат ссылок на + что бы то ни было. Как вы думаете, в чем проблема? Вот один пример:

Пример


person Niki    schedule 21.01.2017    source источник


Ответы (2)


Обновлять

Поиск идет как по текстовому исходному коду, так и по байт-коду; поэтому он показывает вам метод, потому что он нашел #+ в байт-коде, но браузер слишком ограничен, чтобы показать вам и текст, и байты, поэтому он показывает вам только текст (даже если совпадение было выполнено по байт-коду)

Что касается самого байт-кода, Pharo (повторно) компилирует метод каждый раз, когда вы его сохраняете; так, например, когда вы сохраняете следующий метод

Something>>loop
    1 to: 10 do: [ :each | ]

система скомпилирует его, и когда вы проверите метод и посмотрите на байт-код, вы увидите это

введите здесь описание изображения

Я думаю, вы тоже можете написать байт-код вручную.


Оригинальный ответ

Потому что для некоторых специальных селекторов он также просматривает байт-код, который вы можете увидеть, когда проверяете сам метод.

введите здесь описание изображения

Это можно очень легко обнаружить менее чем за минуту, просмотрев код самого Pharo (как только вы освоитесь с Pharo):

  • #+ senders дает вам список отправителей селектора,
  • so you look at the implementation of senders and go through the available call chain (you can select selector with mouse or doubleclick and ctrl+n to show you the senders browser)
    • sendersallSendersOf:thoroughWhichSelectorsReferTo:
  • и там вы видите

.

thoroughWhichSelectorsReferTo: literal
    "Answer a set of selectors whose methods access the argument as a 
    literal. Dives into the compact literal notation, making it slow but 
    thorough "
    | selectors special byte |
    "for speed we check the special selectors here once per class"
    special := Smalltalk
        hasSpecialSelector: literal
        ifTrueSetByte: [ :value | byte := value ].
    selectors := OrderedCollection new.
    self selectorsAndMethodsDo: [ :sel :method |
            ((method refersToLiteral: literal) or: [special and: [method scanFor: byte]]) ifTrue: [selectors add: sel]].
    ^ selectors
person Peter Uhnak    schedule 21.01.2017
comment
Ваш ответ информативный, но я все еще не понимаю. Глядя на код на картинке, которую я загрузил, я не вижу никаких вызовов + или #+. По какому механизму вызов + попадает в байт-код, когда я его на самом деле не пишу. Делает ли это только компиляция? - person Niki; 22.01.2017
comment
Глядя на BytecodeEncoder››#if:isSpecialLiteralForPush: по сути, глядя, где именно система решает включить этот байт-код send:+? - person Niki; 22.01.2017
comment
@Niki Компилятор выполняет множество оптимизаций, иногда вы также можете увидеть <primitive> прагмы в методах (используйте Finder и выберите поиск «Прагмы»), что также приводит к тому, что виртуальная машина ведет себя по-разному. - person Peter Uhnak; 22.01.2017
comment
Теперь я понимаю. Что-то за кулисами делает это, вероятно, из соображений производительности. Кажется, я нашел, где это происходит #genSendSpecial:numArgs: в Encoder. Он делает некоторые утверждения, и в конце это поток nextPut: specialSelectorIndex + 175, я не понимаю, как это генерирует send: + какая логика стоит за этим? - person Niki; 22.01.2017
comment
@Ники см. OCASTTranslator>>emitToDo:step: ; компилятор выполняет семантический анализ и преобразует вызов to:do: в определенный байт-код. Вы также можете выполнить компиляцию вручную (Someclass compile: 'myCode) и проследить выполнение с помощью отладчика. - person Peter Uhnak; 22.01.2017
comment
@Niki, если вы хотите более подробного объяснения, лучше спросить в списке рассылки. - person Peter Uhnak; 22.01.2017
comment
Идеальный! OCASTTranslator››emitToDo:step: именно то, что я искал! - person Niki; 22.01.2017

Просто чтобы немного уточнить ответ @Peter:

Итерация

  1 to: 10 do: [:each | dictionary at: each put: each]

отправляет + 1 в переменную блока each каждый раз, когда он повторяет блок.

Мы не видим + 1 в исходном коде, но на самом деле это происходит за кулисами. Причина обнаружения такой скрытой отправки заключается в том, что Smalltalk анализирует не исходный код, а CompiledMethod, и делает это, просматривая литеральный фрейм и, как говорит Питер, также байт-коды.

Например, путем компиляции и проверки метода

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

Строки 15 и 16 представляют собой + 1, которое происходит на самом деле. Обратите внимание, что символ #+ не находится в литеральной рамке (вкладка Raw инспектора). Его нет и в AST, потому что + отсутствует в исходном коде


Почему это происходит?

Есть несколько специальных сообщений, #to:do: одно из которых, которые встраиваются, когда ваш код их отправляет. Встраивание метода означает включение его байт-кодов в отправителя, а не их фактическую отправку.

Например, если ваш код читает

self foo.
1 to: 10 do: [:i | i foo].
self bar

ваш метод имеет 4 отправки: foo, to:do:, foo (снова) и bar. Однако компилятор создаст только 3 отправки foo, foo и bar и включит байт-коды to:do: на место.

И учитывая, что to:do: необходимо суммировать 1 с аргументом блока i, send +, которое фактически отправляется, обнаруживается как встречающееся в вашем методе (потому что на самом деле это так).

Однако, если вы перепишете метод как

m
  | block |
  block := [:i | i foo].
  self foo.
  1 to: 10 do: block
  self bar

компилятор откажется встраивать to:do: и отправит его как обычное сообщение. В результате ваш метод больше не будет отправителем +.


Если to:do: не отправляется, почему мой метод является его отправителем?

Это более тонкий вопрос. Как мы видели, когда to:do: встроено, оно не отправляется. Как же тогда возможно, что мой метод распознан как отправитель to:do:?

Ну, причина в том, что компилятор все равно добавит символ #to:do: в литеральный фрейм вашего метода. И это будет сделано только для того, чтобы ваш метод был найден как отправитель to:do:. Неважно, действительно ли ваш метод отправляет to:do: или нет, метод встраивания — это просто оптимизация. На более высоком уровне абстракции мы все хотим видеть, что наш метод является «отправителем» to:do:, отсюда и «трюк» добавления его в литеральный фрейм.

person Leandro Caniglia    schedule 21.01.2017
comment
Теперь я понимаю, почему он найден и отображается в результате, в основном в байт-коде есть +, но искатель не может видеть байт-код, поэтому вы видите текст, который на самом деле не содержит +. Где smalltalk решает сделать такое за добавлением сообщений, как это работает? - person Niki; 22.01.2017
comment
Идеальный! Я получаю это сейчас! - person Niki; 22.01.2017