Thanks for the clarifications subbu...This all makes sense. More generically I consider ANY variable which a block you potentially access/manipulate as captured by the block/closure. If you eval in that block the eval can potentially access any variable it can reach from the blocks scope. Since evals are generally dynamically constructed strings we cannot know at compile time if a variable will be accessed or not. This is one reason why eval and binding detection becomes critical. If you pass a binding form the block to another method it can then be used to access the captured variables too. Of course as you said, as you learn more we can all discuss ways of dealing with eval/binding.
-Tom On Fri, Jul 24, 2009 at 12:29 PM, Subramanya Sastry<[email protected]> wrote: > >> > >> > Consider this: >> > >> > o.m1(..) >> > o.m2(..) >> > >> > Since type of o hasn't changed between the 2 calls, you can skip the >> > method >> > table load for the second call. Anyway, I need to understand the call >> > protocol in greater detail to comment more. >> >> This is an interesting use-case for closures. If 'o' is captured in a >> block and made into a proc and executed on another thread, then it >> could potentially change o at any time. So, I think your example >> works most of the time, but could fail in a case like this: >> >> o.m1 { o = something_else } >> o.m2 >> >> Since we do not know when the block can occur we would need to lookup >> o for both calls. Ability to pass around blocks is easy so this >> example probably means any place where o is reachable from another >> scope cannot be shared across calls. > > > > Thanks for expanding on that example .. I guess even a seemingly simple > example can be complex :-) > > Thinking about it some, within the IR, we don't need to worry about this > case because when captured variables are identified, the two 'o's above will > become different objects automatically. > > So, what will happen in this case is: > > --- IR for the block being passed into o.m1 --- > v = frame_load(o) > mtbl = method_table(v) > maddr = lookup(mtbl, "m1") > call(maddr, ..) > v = frame_load(o) > mtbl = method_table(v) > maddr = lookup(mtbl, "m2") > call(maddr, ..) > > So, in this case, the two v's are different objects altogether, and there > the two loads of mtbl are also different, and the optimization to eliminate > the second load gets disabled automatically. > > However in the case where there is no block being passed (o.m1; o.m2), the > IR will be: > > mtbl = method_table(o) > maddr = lookup(mtbl, "m1") > call(maddr, ...) > mtbl = method_table(o) > maddr = lookup(mtbl, "m2") > call(maddr, ...) > > In this case, both method table loads come off the same object (o), and > hence the second load is a candidate for optimization. The only case where > this gets disabled is if the intervening call can completely switch the > method table of the object o. But, depending on how method tables are > implemented in the runtime, evals presumably switch around entries in the > method table, and not the method table itself in which case the optimization > is safe. > > So, the real question here boils down to the previous one: can we safely > determine what local variables are captured by closures? In that sense, > this is not a new special case that we need to worry about, and the method > table opt. falls out of the IR automatically without having to do anything > extra special. All the semantics are captured by the method table load, and > the frame load/store IR instructions. > > >> I originally thought of a scenario like: >> >> Thread.run { o = something_else} >> >> o.m1 >> o.m2 >> >> In this case I would be less worried because execution is dependent on >> a race and would by it's very nature be uncertain. In this case you >> could probably share o and no one would be the wiser. > > That makes sense. This is a clear race condition. That being said, since o > is a captured variable (is this the terminology you use for variables used > in closures that are defined outside the closure?), this scenario is the > same as the block passing example above. So, the two o's will get treated > as different objects and the optimization gets blocked. > > But, thinking a little bit more, the reason the optimization is getting > blocked is because I am assuming conservative semantics for a frame_load, > i.e. frame_loads cannot be optimized away and they always need to be > performed. > > However, we can also use more aggressive semantics where we could consider > eliminating a second frame load of the same variable is considered redundant > if there are no intervening frame-stores or method calls. If we did that, > for this thread example, the second frame load will get removed, which means > the two o's will be considered the same object. But as you noted, this is a > race condition and the compiler is justified in optimizing that code > snippet. > > >> We need to also deal with eval + binding, which >> the JIT currently treats those like keywords, but perhaps we can be a >> little more dynamic with these in the IR? > > I haven't delved into how you currently handle all this so once I understand > that better, let us discuss how to handle them. > > Subbu. > > -- Blog: http://www.bloglines.com/blog/ThomasEEnebo Email: [email protected] , [email protected] --------------------------------------------------------------------- To unsubscribe from this list, please visit: http://xircles.codehaus.org/manage_email
