/*
* Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package org.visage.tools.comp;
import org.visage.api.VisageBindStatus;
import org.visage.api.tree.ForExpressionInClauseTree;
import org.visage.tools.code.VisageClassSymbol;
import org.visage.tools.code.VisageFlags;
import org.visage.tools.code.VisageSymtab;
import org.visage.tools.code.VisageTypes;
import org.visage.tools.code.VisageVarSymbol;
import org.visage.tools.tree.*;
import org.visage.tools.tree.VisageExpression;
import com.sun.tools.mjavac.code.Flags;
import com.sun.tools.mjavac.code.Kinds;
import com.sun.tools.mjavac.code.Scope;
import com.sun.tools.mjavac.code.Symbol;
import com.sun.tools.mjavac.code.Symbol.*;
import com.sun.tools.mjavac.code.Type;
import com.sun.tools.mjavac.code.Type.MethodType;
import com.sun.tools.mjavac.code.TypeTags;
import com.sun.tools.mjavac.util.Context;
import com.sun.tools.mjavac.util.List;
import com.sun.tools.mjavac.util.ListBuffer;
import com.sun.tools.mjavac.util.Name;
import com.sun.tools.mjavac.util.Position;
import java.util.Stack;
/**
* Convert local contexts into classes if need be.
* This conversion is needed for local contexts containing binds,
* on-replace, and some forms of object literal.
*
* Also, local context is created for if Pointer.make is called on a
* local variable or if interpolation attribute is a local variable.
*
* This work is broken into chunks, where a chunk is a body of code
* within which variables can be moved to the top (and initialized
* in-place). Chunk boundaries are:
*
* Class Declaration
* function body
* for-expression body
* while-loop body
* catch body
* on-replace/instanciate body
*
* This code is FRAGILE.
* DO NOT change this code without review from Robert.
*
* @author Robert Field
*/
public class VisageLocalToClass {
private final VisagePreTranslationSupport preTrans;
private final VisageTreeMaker visagemake;
private final VisageDefs defs;
private final Name.Table names;
private final VisageTypes types;
private final VisageSymtab syms;
private final VisageResolve rs;
private VisageEnv<VisageAttrContext> env;
private Symbol owner;
private boolean isStatic;
private Stack<Symbol> prevOwners = new Stack();
private Stack<Boolean> prevIsStatics = new Stack();
private Stack<Type> prevReturnTypes = new Stack<Type>();
protected static final Context.Key<VisageLocalToClass> localToClass =
new Context.Key<VisageLocalToClass>();
public static VisageLocalToClass instance(Context context) {
VisageLocalToClass instance = context.get(localToClass);
if (instance == null) {
instance = new VisageLocalToClass(context);
}
return instance;
}
private VisageLocalToClass(Context context) {
context.put(localToClass, this);
preTrans = VisagePreTranslationSupport.instance(context);
visagemake = VisageTreeMaker.instance(context);
defs = VisageDefs.instance(context);
names = Name.Table.instance(context);
types = VisageTypes.instance(context);
syms = (VisageSymtab)VisageSymtab.instance(context);
rs = VisageResolve.instance(context);
}
public void inflateAsNeeded(VisageEnv<VisageAttrContext> attrEnv) {
this.env = attrEnv;
descend(attrEnv.tree);
}
enum BlockKind {
FUNCTION,
TRIGGER,
LOOP,
CATCH
}
/**
* This class is subclassed by classes which wish to control their descent
* into other chunks.
*
* Where a chunk in a block-expression and all the sharable contexts within.
* Loop bodies (for and while) are not sharable (because they are multiple).
* Class declarations, and function definition are
* separate contexts (thus not sharable).
*
* This class should have NO semantics. Its job it to define the boundaries
* of chunks. It is the sole place this is done.
*/
private abstract class AbstractTreeChunker extends VisageTreeScanner {
abstract void blockWithin(VisageBlock block, BlockKind bkind);
abstract void classWithin(VisageClassDeclaration block);
@Override
public void visitClassDeclaration(VisageClassDeclaration tree) {
// The class is a new chunk
pushOwner(tree.sym, false);
classWithin(tree);
popOwner();
}
@Override
public void visitFunctionValue(VisageFunctionValue tree) {
// Don't process parameters
// The body of the function begins a new chunk
pushOwner(tree.definition.sym, false);
Type returnType = (tree.definition.type == null)?
null : tree.definition.type.getReturnType();
pushFunctionReturnType(returnType);
blockWithin(tree.getBodyExpression(), BlockKind.FUNCTION);
popFunctionReturnType();
popOwner();
}
@Override
public void visitForExpression(VisageForExpression tree) {
for (ForExpressionInClauseTree cl : tree.getInClauses()) {
VisageForExpressionInClause clause = (VisageForExpressionInClause) cl;
// Don't process induction var
scan(clause.getSequenceExpression());
scan(clause.getWhereExpression());
}
// The body of the for-expression begins a new chunk
// Lower has made the body a block-expression
boolean prevStatic = isStatic;
isStatic = false;
pushOwner(preTrans.makeDummyMethodSymbol(owner, defs.boundForPartName), false);
blockWithin((VisageBlock) tree.getBodyExpression(), BlockKind.LOOP);
popOwner();
isStatic = prevStatic;
}
@Override
public void visitWhileLoop(VisageWhileLoop tree) {
scan(tree.cond);
// The body of the while-loop begins a new chunk
// Lower has made the body a block-expression
blockWithin((VisageBlock) tree.getBody(), BlockKind.LOOP);
}
@Override
public void visitCatch(VisageCatch tree) {
// Skip param
// The body of the catch begins a new chunk
blockWithin((VisageBlock) tree.getBlock(), BlockKind.CATCH);
}
@Override
public void visitOnReplace(VisageOnReplace tree) {
pushOwner(preTrans.makeDummyMethodSymbol(owner), false);
blockWithin(tree.getBody(), BlockKind.TRIGGER);
popOwner();
}
/****** Visit methods just to keep the owner straight ******/
//TODO: should this just be on function-value?
@Override
public void visitFunctionDefinition(VisageFunctionDefinition tree) {
pushOwner(tree.sym, false);
super.visitFunctionDefinition(tree);
popOwner();
}
@Override
public void visitInitDefinition(VisageInitDefinition tree) {
pushOwner(tree.sym, false);
super.visitInitDefinition(tree);
popOwner();
}
@Override
public void visitPostInitDefinition(VisagePostInitDefinition tree) {
pushOwner(tree.sym, false);
super.visitPostInitDefinition(tree);
popOwner();
}
@Override
public void visitVar(VisageVar tree) {
pushOwner(preTrans.makeDummyMethodSymbol(owner), tree.isStatic());
scan(tree.getInitializer());
popOwner();
scan(tree.getOnReplace());
scan(tree.getOnInvalidate());
}
}
/**
* Check if a local context needs to be inflated into a class.
* If it has bound variables, variables with triggers, or
* contexts that can reference locals, we need to inflate.
* @param tree Expression to check
* @return True if tree needs to be inflated
*/
private boolean needsToBeInflatedToClass(VisageTree tree) {
class InflationChecker extends AbstractTreeChunker {
boolean needed = false;
void blockWithin(VisageBlock block, BlockKind bkind) {
// Do not descend -- this analysis is within the chunk
}
void classWithin(VisageClassDeclaration klass) {
// Do not descend -- this analysis is within the chunk
// If this block holds a class definition that references a local var that is
// assigned to (and thus cannot be final), we need to inflate the block
needed |= referencesMutatedLocal(klass);
}
@Override
public void visitVar(VisageVar tree) {
// Check for bound or triggered
needed |= tree.isBound();
needed |= tree.getOnReplace() != null;
needed |= tree.getOnInvalidate() != null;
needed |= hasSelfReference(tree);
super.visitVar(tree);
}
@Override
public void visitForExpression(VisageForExpression tree) {
needed |= needsToBeInflatedToClass(tree.getBodyExpression()) && referencesMutatedLocal(tree);
super.visitForExpression(tree);
}
@Override
public void visitCatch(VisageCatch tree) {
needed |= needsToBeInflatedToClass(tree.getBlock()) && referencesMutatedLocal(tree);
super.visitCatch(tree);
}
@Override
public void visitWhileLoop(VisageWhileLoop tree) {
needed |= needsToBeInflatedToClass(tree.getBody()) && referencesMutatedLocal(tree);
super.visitWhileLoop(tree);
}
@Override
public void visitFunctionValue(VisageFunctionValue tree) {
// Funtion value may reference (non-final) locals
needed |= referencesLocal(tree);
super.visitFunctionValue(tree);
}
@Override
public void visitFunctionInvocation(VisageFunctionInvocation tree) {
Symbol msym = VisageTreeInfo.symbol(tree.meth);
// If the function call is the magic Pointer.make(Object)
// function and argument involves a local var, then make a local
// context class.
if (types.isSyntheticPointerFunction(msym)) {
Symbol sym = VisageTreeInfo.symbol(tree.args.head);
if (sym.isLocal()) {
needed = true;
}
} else if (msym != null && (msym.flags() & VisageFlags.BOUND) != 0L) {
// This is a call to a bound function. If any of the arg involves
// a local variable or literal value, then make a local context class
for (VisageExpression arg : tree.args) {
Symbol sym = VisageTreeInfo.symbol(arg);
if (sym != null && sym.isLocal()) {
needed = true;
break;
}
}
} else {
super.visitFunctionInvocation(tree);
}
}
@Override
public void visitBlockExpression(VisageBlock tree) {
if (tree.isBound()) {
needed = true;
}
super.visitBlockExpression(tree);
}
@Override
public void visitInterpolateValue(VisageInterpolateValue tree) {
VisageTag tag = VisageTreeInfo.skipParens(tree.attribute).getVisageTag();
if (tag == VisageTag.IDENT) {
Symbol sym = VisageTreeInfo.symbol(tree.attribute);
if (sym.isLocal()) {
needed = true;
}
} else {
super.visitInterpolateValue(tree);
}
}
}
InflationChecker ic = new InflationChecker();
ic.scan(tree);
return ic.needed;
}
private class VarAndClassConverter extends AbstractTreeChunker {
final VisageClassSymbol classSym;
VarAndClassConverter(VisageClassSymbol classSym) {
this.classSym = classSym;
}
ListBuffer<VisageVar> vars = ListBuffer.lb();
List<VisageTree> varsAsMembers() {
return List.convert(VisageTree.class, vars.toList());
}
void blockWithin(VisageBlock block, BlockKind bkind) {
// Do not descend -- this inflation is within the chunk
}
void classWithin(VisageClassDeclaration block) {
// Do not descend -- this inflation is within the chunk
}
/**
* Convert any variables with the local context into a VarInit
*/
private VisageExpression convertExprAndScan(VisageExpression expr) {
if (expr instanceof VisageVar) {
VisageVar var = (VisageVar) expr;
vars.append(var);
Scope oldScope = preTrans.getEnclosingScope(var.sym);
if (oldScope != null) {
oldScope.remove(var.sym);
}
var.sym.name = preTrans.makeUniqueVarNameIn(var.sym.name, classSym);
if (isStatic) {
var.sym.flags_field |= Flags.STATIC;
var.mods.flags |= Flags.STATIC;
}
classSym.members().enter(var.sym);
var.sym.owner = classSym;
pushOwner(preTrans.makeDummyMethodSymbol(classSym), isStatic);
scan(var.getInitializer());
popOwner();
scan(var.getOnReplace());
scan(var.getOnInvalidate());
// Do the init in-line
VisageExpression vi = visagemake.at(var).VarInit(var);
vi.type = var.type;
return vi;
} else {
// Not a var, just pass through
scan(expr);
return expr;
}
}
@Override
public void visitBlockExpression(VisageBlock tree) {
ListBuffer<VisageExpression> stmts = ListBuffer.lb();
for (VisageExpression stat : tree.stats) {
stmts.append(convertExprAndScan(stat));
}
// Replace the guts of the block-expression with the var-converted versions
tree.stats = stmts.toList();
tree.value = convertExprAndScan(tree.value);
}
}
/**
* Inflate a block-expression into a class:
* {
* var x = 4;
* ++x;
* var y = x + 100;
* def z = bind y + 1;
* println(z);
* x + z
* }
* Should become:
* {
* class local_klass44 {
* var x = 4;
* var y = x + 100;
* def z = bind y + 1;
* function doit$23(0 {
* VarInit x;
* ++x;
* VarInit y;
* ;
* println(z);
* x + z
* }
* }
* (new local_klass44()).doit$();
* }
*/
private void inflateBlockToClass(VisageBlock block, BlockKind bkind) {
final Name funcName = preTrans.syntheticName(defs.doitDollarNamePrefix());
String classNamePrefix;
if (owner instanceof MethodSymbol && (owner.flags() & VisageFlags.BOUND) != 0L) {
classNamePrefix = VisageDefs.boundFunctionClassPrefix;
} else if (owner instanceof MethodSymbol && owner.name == defs.boundForPartName) {
classNamePrefix = VisageDefs.boundForPartClassPrefix;
} else {
classNamePrefix = VisageDefs.localContextClassPrefix;
}
final Name className = preTrans.syntheticName(classNamePrefix);
final VisageClassSymbol classSymbol = preTrans.makeClassSymbol(className, owner);
classSymbol.flags_field |= Flags.FINAL;
final MethodType funcType = new MethodType(
List.<Type>nil(), // arg types
types.normalize(block.type), // return type
List.<Type>nil(), // Throws type
syms.methodClass); // TypeSymbol
final MethodSymbol funcSym = new MethodSymbol(VisageFlags.FUNC_SYNTH_LOCAL_DOIT, funcName, funcType, classSymbol);
class BlockVarAndClassConverter extends VarAndClassConverter {
List<VisageTag> nonLocalExprTags = List.nil();
List<VisageCatch> nonLocalCatchers = List.nil();
Type returnType = null;
BlockVarAndClassConverter() {
super(classSymbol);
}
@Override
public void visitReturn(VisageReturn tree) {
// This is a return in a local context inflated to class
// Handle it as a non-local return
tree.nonLocalReturn = true;
returnType = tree.getExpression() != null ?
topFunctionReturnType() :
syms.voidType;
if (!nonLocalExprTags.contains(tree.getVisageTag())) {
VisageVar param = makeExceptionParameter(syms.visage_NonLocalReturnExceptionType);
VisageReturn catchBody = visagemake.Return(null);
if (returnType.tag != TypeTags.VOID) {
VisageIdent nlParam = visagemake.Ident(param);
nlParam.type = param.type;
nlParam.sym = param.sym;
VisageSelect nlValue = visagemake.Select(nlParam,
defs.value_NonLocalReturnExceptionFieldName, false);
nlValue.type = syms.objectType;
nlValue.sym = rs.findIdentInType(env, syms.visage_NonLocalReturnExceptionType, nlValue.name, Kinds.VAR);
catchBody.expr = visagemake.TypeCast(preTrans.makeTypeTree(returnType), nlValue).setType(returnType);
}
nonLocalExprTags = nonLocalExprTags.append(tree.getVisageTag());
nonLocalCatchers = nonLocalCatchers.append(makeCatchExpression(param, catchBody, returnType));
}
scan(tree.expr);
}
@Override
public void visitBreak(VisageBreak tree) {
// This is a break in a local context inflated to class
// Handle it as a non-local break
tree.nonLocalBreak = true;
if (!nonLocalExprTags.contains(tree.getVisageTag())) {
VisageVar param = makeExceptionParameter(syms.visage_NonLocalBreakExceptionType);
VisageBreak catchBody = visagemake.Break(tree.label);
nonLocalExprTags = nonLocalExprTags.append(tree.getVisageTag());
nonLocalCatchers = nonLocalCatchers.append(makeCatchExpression(param, catchBody, syms.unreachableType));
}
}
@Override
public void visitContinue(VisageContinue tree) {
// This is a continue in a local context inflated to class
// Handle it as a non-local continue
tree.nonLocalContinue = true;
if (!nonLocalExprTags.contains(tree.getVisageTag())) {
VisageVar param = makeExceptionParameter(syms.visage_NonLocalContinueExceptionType);
VisageContinue catchBody = visagemake.Continue(tree.label);
nonLocalExprTags = nonLocalExprTags.append(tree.getVisageTag());
nonLocalCatchers = nonLocalCatchers.append(makeCatchExpression(param, catchBody, syms.unreachableType));
}
}
@Override
public void visitVar(VisageVar tree) {
if ((tree.mods.flags & Flags.PARAMETER) == 0L) {
throw new AssertionError("all vars should have been processed in the block expression");
}
}
private VisageVar makeExceptionParameter(Type exceptionType) {
VisageVar param = visagemake.Param(preTrans.syntheticName(defs.exceptionDollarNamePrefix()), preTrans.makeTypeTree(exceptionType));
param.setType(exceptionType);
param.sym = new VisageVarSymbol(types, names, 0L, param.name, param.type, owner);
return param;
}
private VisageCatch makeCatchExpression(VisageVar param, VisageExpression body, Type bodyType) {
return visagemake.Catch(param,
(VisageBlock)visagemake.Block(0L,
List.<VisageExpression>nil(),
body).setType(bodyType));
}
}
BlockVarAndClassConverter vc = new BlockVarAndClassConverter();
vc.scan(block);
// set position of class etc as block-expression position
visagemake.at(block.pos());
// Create whose vars are the block's vars and having a doit function with the content
VisageType visagetype = visagemake.TypeUnknown();
visagetype.type = block.type;
VisageBlock body = visagemake.Block(block.flags, block.getStmts(), block.getValue());
body.type = block.type;
body.pos = Position.NOPOS;
VisageFunctionDefinition doit = visagemake.FunctionDefinition(
visagemake.Modifiers(VisageFlags.SCRIPT_PRIVATE),
funcName,
visagetype,
List.<VisageVar>nil(),
body);
doit.pos = Position.NOPOS;
doit.sym = funcSym;
doit.type = funcType;
final VisageClassDeclaration cdecl = visagemake.ClassDeclaration(
visagemake.Modifiers(Flags.FINAL | Flags.SYNTHETIC),
className,
List.<VisageExpression>nil(),
vc.varsAsMembers().append(doit));
cdecl.sym = classSymbol;
cdecl.type = classSymbol.type;
types.addVisageClass(classSymbol, cdecl);
cdecl.setDifferentiatedExtendingImplementingMixing(List.<VisageExpression>nil(), List.<VisageExpression>nil(), List.<VisageExpression>nil());
VisageIdent classId = visagemake.Ident(className);
classId.sym = classSymbol;
classId.type = classSymbol.type;
VisageInstanciate inst = visagemake.InstanciateNew(classId, null);
inst.sym = classSymbol;
inst.type = classSymbol.type;
VisageSelect select = visagemake.Select(inst, funcName, false);
select.sym = funcSym;
select.type = funcSym.type;
VisageFunctionInvocation apply = visagemake.Apply(null, select, null);
apply.type = block.type;
List<VisageExpression> stats = List.<VisageExpression>of(cdecl);
VisageExpression value = apply;
if (vc.nonLocalCatchers.size() > 0) {
VisageBlock tryBody = (VisageBlock)visagemake.Block(0L, stats, value).setType(block.type);
stats = List.<VisageExpression>of(
visagemake.Try(
tryBody,
vc.nonLocalCatchers,
null));
if (block.type != syms.voidType) {
VisageVarSymbol resVarSym = new VisageVarSymbol(types,
names,
0L,
preTrans.syntheticName(defs.resDollarNamePrefix()),
types.normalize(block.type),
doit.sym);
VisageVar resVar = visagemake.Var(resVarSym.name,
preTrans.makeTypeTree(resVarSym.type),
visagemake.Modifiers(resVarSym.flags_field),
null,
VisageBindStatus.UNBOUND,
null, null);
resVar.type = resVarSym.type;
resVar.sym = resVarSym;
VisageIdent resVarRef = visagemake.Ident(resVarSym);
resVarRef.sym = resVarSym;
resVarRef.type = resVar.type;
tryBody.value = visagemake.Assign(resVarRef, apply).setType(block.type);
value = (bkind == BlockKind.FUNCTION &&
vc.returnType != null &&
vc.returnType.tag != TypeTags.VOID) ?
visagemake.Return(
visagemake.TypeCast(
preTrans.makeTypeTree(vc.returnType),
resVarRef
).setType(vc.returnType)
).setType(syms.unreachableType) :
resVarRef;
stats = stats.prepend(resVar);
}
else {
value = null;
}
}
preTrans.liftTypes(cdecl, classSymbol.type, funcSym);
// Replace the guts of the block-expression with the class wrapping the previous body
// and a call to the doit function of that class.
block.stats = stats;
block.value = value;
}
/**
* Flatten any vars defined in var initializer block expressions into the class level
*/
private void flattenVarsIntoClass(VisageClassDeclaration klass) {
final VisageClassSymbol classSym = (VisageClassSymbol) klass.sym;
VarAndClassConverter vc = new VarAndClassConverter(classSym);
for (VisageTree mem : klass.getMembers()) {
if (needsToBeInflatedToClass(mem)) {
vc.scan(mem);
}
}
klass.setMembers(klass.getMembers().appendList(vc.varsAsMembers()));
}
/**
* Descend to the next level of chunk
*/
private void descend(VisageTree tree) {
BottomUpChunkWalker bucw = new BottomUpChunkWalker();
bucw.scan(tree);
}
private class BottomUpChunkWalker extends AbstractTreeChunker {
void blockWithin(VisageBlock block, BlockKind bkind) {
// Descend into inner chunks
descend(block);
// check if the block needs inflation, if so, inflate
if (needsToBeInflatedToClass(block)) {
inflateBlockToClass(block, bkind);
}
}
void classWithin(VisageClassDeclaration klass) {
for (VisageTree member : klass.getMembers()) {
descend(member);
}
flattenVarsIntoClass(klass);
}
}
/************************** Utilities ******************************/
private boolean referencesMutatedLocal(VisageTree tree) {
class ReferenceChecker extends VisageTreeScanner {
boolean hasMutatedLocal = false;
@Override
public void visitIdent(VisageIdent tree) {
if (tree.sym instanceof VarSymbol) {
VisageVarSymbol vsym = (VisageVarSymbol) tree.sym;
if (vsym.isMutatedLocal()) {
hasMutatedLocal = true;
}
}
}
}
ReferenceChecker rc = new ReferenceChecker();
rc.scan(tree);
return rc.hasMutatedLocal;
}
private boolean referencesLocal(VisageTree tree) {
class ReferenceChecker extends VisageTreeScanner {
boolean hasLocal = false;
@Override
public void visitIdent(VisageIdent tree) {
if (tree.sym instanceof VarSymbol) {
VisageVarSymbol vsym = (VisageVarSymbol) tree.sym;
if (vsym.isLocal()) {
hasLocal = true;
}
}
super.visitIdent(tree);
}
@Override
public void visitIndexof(VisageIndexof tree) {
hasLocal = true;
super.visitIndexof(tree);
}
}
ReferenceChecker rc = new ReferenceChecker();
rc.scan(tree);
return rc.hasLocal;
}
private boolean hasSelfReference(VisageVar checkedVar) {
return checkedVar.sym.hasSelfReference();
}
private void pushOwner(Symbol newOwner, boolean newIsStatic) {
prevOwners.push(owner);
prevIsStatics.push(isStatic);
owner = newOwner;
isStatic = newIsStatic;
}
private void popOwner() {
owner = prevOwners.pop();
isStatic = prevIsStatics.pop();
}
private void pushFunctionReturnType(Type returnType) {
prevReturnTypes.push(returnType);
}
private void popFunctionReturnType() {
prevReturnTypes.pop();
}
private Type topFunctionReturnType() {
return prevReturnTypes.peek();
}
}