On Thu, 27 Jun 2013, Marcus Denker wrote:
On Jun 27, 2013, at 8:16 AM, Clément Bera <[email protected]> wrote:
In Pharo 3 we will support conditional optimized messages ( #ifTrue:,
#ifFalse:, #ifTrue:ifFalse: and #ifFalse:ifTrue: ) sent on non boolean
receiver. However, this feature will be slow, so one should not use it too much
(as #become:, #isKindOf:, ...). Basically Opal compiles on the fly without
optimizing it the conditional messageNode as a DoIt, run it, and jump in the
context to the pc corresponding to the next ast node. We don't generate extra
bytecode as you suggested, because it will grow the size of the image a lot and
slow down the system with extra conditions.
It's actually not a lot of code. We need to debug/fix it for all the cases,
though, this might add some more complexity:
So Opal generates the same code as the old compiler. It's just a different
implementation of #mustBeBoolean, what makes the difference.
IIUC whenever the receiver of the optimized conditional message is not
true or false, the code is decompiled, then a new CompiledMethod is
created without the optimization, it's injected onto the stack, and the
execution is continued from before where the jumpTrue: or jumpFalse: was
in the original method.
As Clément wrote, this is rather slow. Maybe even too slow to be useful. A
possible improvement would be to permanently replace the original compiled
method with the deoptimized one, since the receiver is likely to be not
one of the unique Boolean instances during futher executions of the
method. This way the decompilation has to be done only once.
Though these kind of code mutations behind the scene can cause some
headache for someone who's trying to optimize the code.
Levente
mustBeBooleanInMagic: context
"Permits to redefine methods inlined by compiler.
Take the ast node corresponding to the mustBeBoolean error, compile it on
the fly with Opal and executes it as a DoIt. Then resume the execution of the
context."
| proceedValue sendNode selector expression arguments methodNode
method offset position |
context skipBackBeforeJump.
sendNode := context sourceNode sourceNodeForPC: context pc.
position := sendNode irInstruction bytecodeOffset.
offset := sendNode irInstruction nextBytecodeOffsetAfterJump -
position.
expression := sendNode copy asSequenceNode transformLastToReturn.
selector := #ExecuteUnOptimzedIn:. arguments := {(RBVariableNode
named:'ThisContext')}.
methodNode := RBMethodNode selector: selector arguments: arguments
body: expression.
context tempNames do: [:tempName |
methodNode :=methodNode rewriteTempNamedWrite: tempName
forContext: context.
methodNode :=methodNode rewriteTempNamedRead: tempName
forContext: context.
].
methodNode compilationContext: sendNode methodNode compilationContext.
methodNode compilationContext
class: UndefinedObject;
compilerOptions: #(+ optIlineNone).
method := methodNode generateWithSource.
context jump: offset.
proceedValue := self withArgs: {context} executeMethod: method.
^proceedValue