Reputation: 95
Opened a fresh Pharo 5 image, opened Finder and searched for the + selector. Then selected the + and clicked Senders. Got about 5000 results, most of them seem fine, however upon closer inspection some results seem to missing any reference of my message send.
Not a few I might add, about 1700 of them seem to be erroneous and contain no reference of + what so ever. What do you think is the problem? Here's one example:
Upvotes: 4
Views: 95
Reputation: 14858
Just to clarify @Peter's answer a little bit:
The iteration
1 to: 10 do: [:each | dictionary at: each put: each]
sends + 1
to the block variable each
every time it iterates the block.
We do not see + 1
in the source code, but it actually happens behind the scenes. The reason why such a hidden send is found is that Smalltalk does not analyze the source code but the CompiledMethod
and does that by looking at the literal frame and, as Peter says, also at the bytecodes.
For instance, by compiling and inspecting the method
m
1 to: 10 do: [:i | i foo]
we can see that the intermediate representation Ir tab reads:
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
Lines 15 and 16 represent the + 1
that actually takes place. Note that the symbol #+
is not in the literal frame (Raw tab of the inspector). It is not in the AST either because +
is absent from the source code
Why this happens?
There are some special messages, #to:do:
being one of them, that get inlined when your code sends them. Inlining a method means including its bytecodes in the sender rather than actually sending them.
For example, if your code reads
self foo.
1 to: 10 do: [:i | i foo].
self bar
your method has 4 sends: foo
, to:do:
, foo
(again) and bar
. However, the compiler will produce only 3 sends foo
, foo
and bar
and will include the bytecodes of to:do:
in place.
And given that to:do:
needs to sum 1
to the block argument i
, the send +
, which is actually sent, is detected as occurring in your method (because it fact, it does).
However, if you rewrite the method as
m
| block |
block := [:i | i foo].
self foo.
1 to: 10 do: block
self bar
the compiler will refuse to inline to:do:
and will send it as a regular message. As a result your method will not be a sender of +
any longer.
If to:do: is not sent, why my method is a sender of it?
This is a subtler question. As we have seen, when to:do:
is inlined, it is not sent. How then it is possible that my method is recognized as a sender of to:do:
?
Well, the reason is that the compiler will anyway add the symbol #to:do:
to the literal frame of your method. And it will do that just for allowing your method to be found as a sender of to:do:
. It doesn't matter whether your method actually sends to:do:
or not, the inlining technique is just an optimization. At a higher level of abstraction we all want to see that our method is a "sender" of to:do:
, hence the "trick" of adding it to the literal frame.
Upvotes: 4
Reputation: 10217
The search goes both through the textual source code and the bytecode; so it shows you the method, because it found #+
in the bytecode, but the browser is too limited to show you both text and bytes, so it shows you only the text (even though the match was done on bytecode)
As for bytecode itself, Pharo (re)compiles a method every time you save it; so for example when you save the following method
Something>>loop
1 to: 10 do: [ :each | ]
the system will compile it, and when you inspect the method and look at the bytecode, you will see this
You can write the bytecode by hand too I believe.
Because for some special selectors it also looks through the bytecode, which you can see when you inspect the method itself.
This can be very easily discovered in less than a minute by looking through the code of Pharo itself (once you are more comfortable with Pharo):
#+ senders
gives you the list of senders of a selector,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)
senders
→ allSendersOf:
→ 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
Upvotes: 6