优化 ifTrue:ifFalse:在 Smalltalk 中
Optimising ifTrue:ifFalse: in Smalltalk
Smalltalk Blue Book 说编译器优化了 ifTrue:ifFalse:
和使用特殊操作码的朋友,以避免必须创建闭包并对实际布尔对象进行动态调度。
如果我理解正确,那么:
o ifTrue: [ ^ 'yes' ] ifElse: [ ^ 'no' ]
...变成类似:
push o
jump_if_false 1
return 'yes'
jump 2
1:
return 'no'
2:
(我检查过,GNU Smalltalk 正是这样做的。)
如果 o
是 Boolean
,这会很好地工作,但我不明白如果不是的话会发生什么。毕竟,编译器 不知道o
是什么类型。它不能保证它是布尔值。它甚至不知道 ifTrue:ifFalse:
方法是否具有传统语义。
例如:
Fnord ifTrue: tb ifFalse: fb
"muhahaha"
tb value: self.
fb value: self and: 9.
^ 7
要使此实现起作用,true 和 false 块 必须 是闭包,分别采用一个和两个参数。那么编译器如何知道生成闭包呢?还是允许编译器在这种情况下中断?
这是字节码(在我使用的方言中)
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
这是本地化代码(简体):
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
如您所见,是 JIT 编译器(a.k.a.nativizer)检查 #ifTrue:ifFalse:
的接收者是否为布尔值。更准确地说,如果接收者不是布尔值,test jump false
字节码的原生化会发出将发送 mustBeBoolean
通知的代码。
编辑
关于你的第二个问题,如果发件人(明确地)内联块参数,则另一个 class 中的 #ifTrue:ifFalse:
的实现将不起作用:
receiver ifTrue: ['yes'] ifFalse: ['no']
如果 receiver
是 Fnord
的实例(即使 receiver
理解 #ifTrue:ifFalse:
,此代码将失败并返回 #mustBeBoolean
。原因是,在这种情况下,编译器将像我们上面看到的那样优化 #ifTrue:ifFalse:
而不是将其作为常规消息发送。
但是,表达式
receiver ifTrue: [:a | <whatever>] ifFalse: [:a :b | <your code>]
和
fa := [:a | <whatever>].
fb := [:a :b | <yourcode>].
receiver ifTrue: fa ifFalse: fb
不会优化,意思是#ifTrue:ifFalse:
会发
Smalltalk Blue Book 说编译器优化了 ifTrue:ifFalse:
和使用特殊操作码的朋友,以避免必须创建闭包并对实际布尔对象进行动态调度。
如果我理解正确,那么:
o ifTrue: [ ^ 'yes' ] ifElse: [ ^ 'no' ]
...变成类似:
push o
jump_if_false 1
return 'yes'
jump 2
1:
return 'no'
2:
(我检查过,GNU Smalltalk 正是这样做的。)
如果 o
是 Boolean
,这会很好地工作,但我不明白如果不是的话会发生什么。毕竟,编译器 不知道o
是什么类型。它不能保证它是布尔值。它甚至不知道 ifTrue:ifFalse:
方法是否具有传统语义。
例如:
Fnord ifTrue: tb ifFalse: fb
"muhahaha"
tb value: self.
fb value: self and: 9.
^ 7
要使此实现起作用,true 和 false 块 必须 是闭包,分别采用一个和两个参数。那么编译器如何知道生成闭包呢?还是允许编译器在这种情况下中断?
这是字节码(在我使用的方言中)
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
这是本地化代码(简体):
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
如您所见,是 JIT 编译器(a.k.a.nativizer)检查 #ifTrue:ifFalse:
的接收者是否为布尔值。更准确地说,如果接收者不是布尔值,test jump false
字节码的原生化会发出将发送 mustBeBoolean
通知的代码。
编辑
关于你的第二个问题,如果发件人(明确地)内联块参数,则另一个 class 中的 #ifTrue:ifFalse:
的实现将不起作用:
receiver ifTrue: ['yes'] ifFalse: ['no']
如果 receiver
是 Fnord
的实例(即使 receiver
理解 #ifTrue:ifFalse:
,此代码将失败并返回 #mustBeBoolean
。原因是,在这种情况下,编译器将像我们上面看到的那样优化 #ifTrue:ifFalse:
而不是将其作为常规消息发送。
但是,表达式
receiver ifTrue: [:a | <whatever>] ifFalse: [:a :b | <your code>]
和
fa := [:a | <whatever>].
fb := [:a :b | <yourcode>].
receiver ifTrue: fa ifFalse: fb
不会优化,意思是#ifTrue:ifFalse:
会发