| /* |
| * Copyright 2010 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.dev.jjs.impl; |
| |
| import com.google.gwt.dev.jjs.SourceInfo; |
| import com.google.gwt.dev.jjs.ast.Context; |
| import com.google.gwt.dev.jjs.ast.JBlock; |
| import com.google.gwt.dev.jjs.ast.JDeclarationStatement; |
| import com.google.gwt.dev.jjs.ast.JForStatement; |
| import com.google.gwt.dev.jjs.ast.JLocal; |
| import com.google.gwt.dev.jjs.ast.JLocalRef; |
| import com.google.gwt.dev.jjs.ast.JMethodBody; |
| import com.google.gwt.dev.jjs.ast.JModVisitor; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JStatement; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.jjs.ast.JVariable; |
| import com.google.gwt.dev.jjs.ast.JVisitor; |
| import com.google.gwt.dev.util.collect.HashSet; |
| |
| import java.util.BitSet; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| /** |
| * Utility base class for visitors that need to replace expressions with temp |
| * locals. This class specifically handles allocating temp locals, and inserting |
| * a {@link JDeclarationStatement} at the appropriate point. It tracks scopes |
| * and assigns any potentially conflicting uses with unique names. Subclasses |
| * are only allowed to use a temp within the {@link JBlock} within which it is |
| * allocated. Non-conflicting temp locals attempt to reuse as many names as |
| * possible in order to produce the most optimal output. |
| * |
| * <p> |
| * Subclasses must always visit a {@link JMethodBody} naturally, rather than |
| * individual blocks. |
| * </p> |
| */ |
| public abstract class TempLocalVisitor extends JModVisitor { |
| /* |
| * TODO(scottb): right now our handling of for statements is sub-optimal. |
| * Technically, a for statement creates an implicit scope that is not part of |
| * any block. This scope is a child of the block containing the for statement, |
| * and a parent of the for statement's action block. We don't presently model |
| * this scope, instead we just allow the for statement's top-level constructs |
| * to bleed up into the containing block, which is correct if sub-optimal for |
| * name reuse. |
| */ |
| |
| /** |
| * Creates a Scope for each JBlock in the current method body. |
| */ |
| private static class CollectScopes extends JVisitor { |
| |
| private Scope curScope = null; |
| |
| private final Map<JBlock, Scope> scopes; |
| |
| public CollectScopes(Map<JBlock, Scope> scopes) { |
| this.scopes = scopes; |
| } |
| |
| @Override |
| public void endVisit(JBlock x, Context ctx) { |
| exit(x); |
| } |
| |
| @Override |
| public void endVisit(JDeclarationStatement x, Context ctx) { |
| JVariable target = x.getVariableRef().getTarget(); |
| if (target instanceof JLocal) { |
| String name = target.getName(); |
| if (name.startsWith(PREFIX)) { |
| curScope.recordTempAllocated(Integer.parseInt(name.substring(PREFIX.length()), 10)); |
| } |
| } |
| } |
| |
| @Override |
| public boolean visit(JBlock x, Context ctx) { |
| enter(x); |
| return true; |
| } |
| |
| private void enter(JBlock x) { |
| curScope = new Scope(curScope); |
| scopes.put(x, curScope); |
| } |
| |
| private void exit(JBlock x) { |
| assert scopes.get(x) == curScope; |
| curScope = curScope.parent; |
| } |
| } |
| |
| /** |
| * Represents a single logical scope (ie, JBlock), and tracks allocation and |
| * usage of any temps. |
| */ |
| private static class Scope { |
| /** |
| * My containing scope, will be <code>null</code> if this scope corresponds |
| * to the top-level method body block. |
| */ |
| public final Scope parent; |
| |
| /** |
| * Caches which temps have been allocated both in this scope and all parent |
| * scopes. |
| */ |
| private transient BitSet allAllocated; |
| |
| /** |
| * Caches the last temp allocated in this scope, for speedier lookup. |
| */ |
| private transient int lastTemp; |
| |
| /** |
| * The set of all temps allocated by all my child blocks. This prevents me |
| * from allocating a temp one of my children already allocated; however it |
| * does not prevent my children from reusing temps also used by their |
| * siblings. |
| */ |
| private final BitSet myChildTemps = new BitSet(); |
| |
| /** |
| * The set of temps that have been directly allocated in this scope. |
| */ |
| private final BitSet myTemps = new BitSet(); |
| |
| public Scope(Scope parent) { |
| this.parent = parent; |
| } |
| |
| /** |
| * Acquires the next free temp in this scope. |
| */ |
| public int allocateNextFreeTemp() { |
| if (allAllocated == null) { |
| allAllocated = new BitSet(); |
| // Any temps already allocated by my parents are not available to me. |
| for (Scope it = this; it != null; it = it.parent) { |
| allAllocated.or(it.myTemps); |
| } |
| } |
| // Any temps already allocated by my children are not available to me. |
| allAllocated.or(myChildTemps); |
| // Find the next free temp. |
| lastTemp = allAllocated.nextClearBit(lastTemp); |
| recordTempAllocated(lastTemp); |
| return lastTemp; |
| } |
| |
| /** |
| * Called when entering this scope, clears transient state. |
| */ |
| public void enter() { |
| // Assume dirty, lazy recompute. |
| allAllocated = null; |
| lastTemp = 0; |
| } |
| |
| /** |
| * Called when exiting this scope. |
| */ |
| public void exit() { |
| // Free the memory. |
| allAllocated = null; |
| } |
| |
| /** |
| * Record a temp as being allocated in this scope. |
| */ |
| public void recordTempAllocated(int tempNumber) { |
| assert !myTemps.get(tempNumber); |
| // Record my own usage. |
| myTemps.set(tempNumber); |
| if (allAllocated != null) { |
| allAllocated.set(tempNumber); |
| } |
| // Tell all my parents I'm now using this one. |
| for (Scope it = this.parent; it != null; it = it.parent) { |
| assert !it.myTemps.get(tempNumber); |
| it.myChildTemps.set(tempNumber); |
| } |
| } |
| } |
| |
| /** |
| * Prefix for temp locals. |
| */ |
| private static final String PREFIX = "$t"; |
| |
| /** |
| * A set of statements we cannot insert declaration statements into. Currently |
| * this is just the "increments" list of a JForStatement. |
| */ |
| private final Set<JStatement> banList = new HashSet<JStatement>(); |
| |
| private JMethodBody curMethodBody = null; |
| private Scope curScope = null; |
| private final Stack<Context> insertionStack = new Stack<Context>(); |
| private Map<JBlock, Scope> scopes = null; |
| |
| @Override |
| public final void endVisit(JBlock x, Context ctx) { |
| exit(x); |
| super.endVisit(x, ctx); |
| } |
| |
| @Override |
| public final void endVisit(JMethodBody x, Context ctx) { |
| curMethodBody = null; |
| scopes = null; |
| super.endVisit(x, ctx); |
| } |
| |
| @Override |
| public final void endVisit(JStatement x, Context ctx) { |
| if (ctx.canInsert()) { |
| if (!banList.remove(x)) { |
| Context popped = insertionStack.pop(); |
| assert popped == ctx; |
| } |
| } |
| super.endVisit(x, ctx); |
| } |
| |
| @Override |
| public final boolean visit(JBlock x, Context ctx) { |
| enter(x); |
| return super.visit(x, ctx); |
| } |
| |
| @Override |
| public final boolean visit(JMethodBody x, Context ctx) { |
| curMethodBody = x; |
| scopes = new HashMap<JBlock, Scope>(); |
| new CollectScopes(scopes).accept(x); |
| return super.visit(x, ctx); |
| } |
| |
| @Override |
| public final boolean visit(JStatement x, Context ctx) { |
| if (ctx.canInsert() && !banList.contains(x)) { |
| insertionStack.push(ctx); |
| } |
| if (x instanceof JForStatement) { |
| // Cannot add decl statements to a for statement increments list. |
| JForStatement forStmt = (JForStatement) x; |
| banList.addAll(forStmt.getIncrements()); |
| } |
| return super.visit(x, ctx); |
| } |
| |
| protected JLocal createTempLocal(SourceInfo info, JType type) { |
| int tempNum = curScope.allocateNextFreeTemp(); |
| String name = PREFIX + tempNum; |
| JLocal local = JProgram.createLocal(info, name, type, false, curMethodBody); |
| JDeclarationStatement init = new JDeclarationStatement(info, new JLocalRef(info, local), null); |
| insertionStack.peek().insertBefore(init); |
| return local; |
| } |
| |
| private boolean enter(JBlock x) { |
| Scope enterScope = scopes.get(x); |
| assert enterScope.parent == curScope; |
| curScope = enterScope; |
| enterScope.enter(); |
| return true; |
| } |
| |
| private void exit(JBlock x) { |
| assert scopes.get(x) == curScope; |
| curScope.exit(); |
| curScope = curScope.parent; |
| } |
| } |