Here goes my attempt at this problem.
After Cleanup, whether via bytecode emitter or via nsc.transform.Transform, the algorithm to guarantee empty-stack-on-try-entry is mostly the same. Unlike conversion to three-address form the following achieves empty-stack-on-try-entry while minimizing extra temporary variables.
Usually a Transform visits children in order 1 to n. In our case instead n to 1 is needed, as can be seen more clearly for a Tree node standing for an n-ary operation (to recap, the bytecode instructions for that node push n values onto the operand stack, consume them, and may leave a value for the result on the stack). Before emitting as just suggested, we need to know what the highest argument a_i is that encloses a Try node, say a_m , so as to emit instead:
Block(
val temp_1 = arg_1
. . .
val temp_m = arg_m
Op(temp_1 ... temp_m , arg_(m+1) ... arg_n)
)
(In the notation above, the receiver of an instance-method is shown as a_1 , akin to bytecode)
Besides n-ary operations with n > 1, the other nodes of interest are:
(a) New with one or more constructor arguments. The difference with n-ary op with n > 1 has to do with the way the code emitter lowers New:
NEW C
DUP
. . . instructions loading ctor-args
INVOKESPECIAL <init>
ie the first ctor-arg already finds a non-empty stack, unlike for a unary-op.
(b) Try
The candidate solution (pseudocode below) fills in details (e.g. what to do for non-op nodes like Block). It's formulated in terms of Transform, which should be fast enough because most transforms are the identity, and a lazy tree copier is our friend. The additional time this transform takes should be more than offset by reduced IntRefs etc resulting from dropping all code for liftTree from UnCurry.
As the example of n-ary ops shows, the status of the visitor changes upon a successor node being a Try (due to reverse visit order, we've already been there by the time the current node is visited). We represent this "status" as boolean field of our Transform subclass.
The cases and corresponding actions for the current node are:
case Op(a_1 ... a_n) with n > 1
| New(...) with one ore more args
=>
val saved = status
status = false
var highest = -1 // position of the last argument for which a temp var is needed
// (for all args coming before a temp var will be needed too)
for i <- n to 1 {
val xformed_i = transform(arg_i) // visiting this successor node may set the status field
if status && (i > highest) {
highest = i
}
}
if status {
assert highest != -1
// now comes the transformed node to return
Block(
ValDef(temp_1, xformed_1)
. . .
ValDef(temp_highest, xformed_highest)
Op /* resp. New */ (temp_1 ... temp_highest , xformed_(highest+1) ... xformed_n )
)
} else {
super.transform(tree) // identity transform (to recap, with children visited in reverse order)
}
status |= saved
case Try
=> status = true // sticky status, gets propagated to all previous nodes (ie those visited later in our reverse visit order)
case _
=> super.transform
Looks like that's it. Well almost (paragraph below). Only the cases above? What about Block(stmt_1 ... stmt_n expr) ? The identity transform should be enough: consider a Try enclosed in some stmt_i. Upon being visited, status becomes true, but all stmts before stmt_i are not touched. That's fine: their values will be cleared from the operand stack by the time a next stmt starts. Same goes for expr. By setting status however, code preceding the Block in question is requested to leave an empty stack.
Why "almost"? The above tells us in advance what temporary vars to create and how to drain the stack, assuming there's nothing yet on the stack. An exception handler however has on entry an exception on the stack. But that can be stored (usually is) into a temp var anyway. Thus the Try case above needs to take care of that before setting status = true.
As with any other candidate solution, the above assumes the pipeline from UnCurry till CleanUp can handle (in practice) Try nodes in any expression position (so far, they showed up only in statement poisition, or that was the idea, thanks to UnCurry's liftTree)
Miguel
http://lampwww.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/