Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optimising ifTrue:ifFalse: in Smalltalk

Tags:

smalltalk

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?

like image 257
David Given Avatar asked Sep 26 '22 17:09

David Given


1 Answers

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.

like image 194
Leandro Caniglia Avatar answered Oct 18 '22 04:10

Leandro Caniglia