The Smalltalk Blue Book says that the compiler optimises ifTrue:ifFalse:
and friends using special opcodes, so as to avoid having to create closures and do dynamic dispatch to the actual Boolean object.
If I've understood this correctly, then:
o ifTrue: [ ^ 'yes' ] ifElse: [ ^ 'no' ]
...becomes something like:
push o
jump_if_false 1
return 'yes'
jump 2
1:
return 'no'
2:
(And I checked, and GNU Smalltalk does exactly this.)
This would work fine if o
is a Boolean
, but I don't understand what would happen if it isn't. After all, the compiler doesn't know what type o
is. It can't guarantee it's a Boolean. It doesn't even know whether the ifTrue:ifFalse:
method has the traditional semantics.
For example:
Fnord ifTrue: tb ifFalse: fb
"muhahaha"
tb value: self.
fb value: self and: 9.
^ 7
For this implementation to work, the true and false blocks must be closures, taking one and two parameters respectively. So how does the compiler know to generate the closures? Or is the compiler allowed to break in this situation?
Here are the bytecodes (in the dialect I'm using)
1 frameless prolog
2 load R with instance o ; o is an ivar in my code
4 test jump false 13
7 load R with literal 'yes'
9 return
10 jump 16
13 load R with literal 'no'
15 return
16 return self
And here is the nativized code (simplified):
cmp eax, false ; test jump false
jz @2
cmp eax, true
jz @1
call mustBeBoolean ; <--- here!
@1: mov eax, 'true'
ret
jmp @3 ; jump
@2: mov eax, 'no'
ret
@3: move eax, esi
ret
As you can see, it is the JIT Compiler (a.k.a. nativizer) who checks whether the receiver of #ifTrue:ifFalse:
is a Boolean or not. More precisely, the nativization of the test jump false
bytecode emits code that will send the mustBeBoolean
notification if the receiver isn't a Boolean.
EDIT
With respect to your second question, the implementation of #ifTrue:ifFalse:
in another class would not work if the sender inlines (explicitly) the block arguments as in:
receiver ifTrue: ['yes'] ifFalse: ['no']
This code will fail with #mustBeBoolean
if receiver
is an instance of Fnord
(even though receiver
understands #ifTrue:ifFalse:
.) The reason is that, in this case, the compiler will optimize #ifTrue:ifFalse:
as we saw above instead of sending it as a regular message.
However, the expressions
receiver ifTrue: [:a | <whatever>] ifFalse: [:a :b | <your code>]
and
fa := [:a | <whatever>].
fb := [:a :b | <yourcode>].
receiver ifTrue: fa ifFalse: fb
will not be optimized, meaning that #ifTrue:ifFalse:
will be sent.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With