/*
* This file is part of the X10 project (http://x10-lang.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* (C) Copyright IBM Corporation 2006-2010.
*/
package x10c.visit;
import java.util.Stack;
import polyglot.ast.Call;
import polyglot.ast.Cast;
import polyglot.ast.Eval;
import polyglot.ast.Expr;
import polyglot.ast.Field;
import polyglot.ast.FieldAssign;
import polyglot.ast.LocalAssign;
import polyglot.ast.LocalDecl;
import polyglot.ast.Node;
import polyglot.ast.NodeFactory;
import polyglot.ast.Receiver;
import polyglot.ast.Return;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import polyglot.util.Position;
import polyglot.visit.NodeVisitor;
import x10.ast.ClosureCall;
import x10.ast.Closure_c;
import x10.ast.ParExpr;
import x10.ast.X10Call;
import x10.ast.X10MethodDecl;
import x10.ast.X10New;
import x10.types.MethodInstance;
import x10.types.checker.Converter;
import x10.visit.X10PrettyPrinterVisitor;
import x10c.ast.X10CNodeFactory_c;
import x10c.types.X10CTypeSystem_c;
/**
* This pass adds boxing AST nodes to mark necessary boxing
* conversion in places, where it is not easy to make a judgement
* about boxing directly in codegen, for example, in field assignments,
* where the codegen cannot know, whether the assignment expression
* value is used or not.
*/
public class BoxingDetector extends NodeVisitor {
private final X10CTypeSystem_c xts;
private final X10CNodeFactory_c xnf;
private final Stack<Type> returnType = new Stack<Type>();
public BoxingDetector( TypeSystem ts, NodeFactory nf) {
xts = (X10CTypeSystem_c) ts;
xnf = (X10CNodeFactory_c) nf;
}
@Override
public NodeVisitor enter(Node parent, Node n) {
// keep track of the return type of the enclosing method
if (n instanceof X10MethodDecl) {
X10MethodDecl decl = (X10MethodDecl)n;
returnType.push(decl.returnType().type());
}
return this;
}
@Override
public Node leave(Node parent, Node old, Node n, NodeVisitor v) {
// do context bookkeeping first
if (old instanceof X10MethodDecl) {
assert ((X10MethodDecl)old).returnType().type() == returnType.peek();
returnType.pop();
}
// NB: for calls and closure calls, the rules for boxing are complicated,
// so we insert a simple Cast AST node, so that codegen makes a decision
// whether to do boxing/unboxing. The goal is to ensure, that
// the only places, where codegen needs to consider boxing/unboxing
// are:
// * casts
// * method or closure call arguments
// Generic methods (return type T) can return boxed values (e.g., x10.core.UInt)
// but in the type instantiated [UInt] subclass it is treated as unboxed int.
// We need to insert explicit cast to
// an unboxed type, so that proper conversion method call is inserted later.
if (n instanceof X10Call && !(parent instanceof Eval)) {
X10Call call = (X10Call)n;
Receiver target = call.target();
MethodInstance mi = call.methodInstance();
Type expectedReturnType = call.type();
// do not insert cast the expected type is already boxed or is void
if (X10PrettyPrinterVisitor.isBoxedType(expectedReturnType) || expectedReturnType.isVoid() // (void) never needs boxing
// or if the method def type is void -- it is a synthetic method without a proper definition
|| mi.def().returnType().get().isVoid()) return n;
if (X10PrettyPrinterVisitor.isBoxedType(mi.def().returnType().get())) {
// only insert cast if the actual returned type is boxed
return cast(call, expectedReturnType);
}
}
// Closures may be implemented by methods returning boxed or unboxed values,
// depending on some involved condition that is checked in codegen.
// So add the dummy type cast to give the codegen
// chance to handle unboxing in a single place
if (n instanceof ClosureCall && !(parent instanceof Eval) && !(parent instanceof Cast)) {
ClosureCall call = (ClosureCall)n;
// if the return type is not primitive, then unboxing will not be needed
if (X10PrettyPrinterVisitor.isBoxedType(call.type()) || call.type().isVoid()) return n;
return cast(call, call.type());
}
if (n instanceof Expr) { // boxing may be needed only for expressions
Expr expr = (Expr)n;
// parent node still has "old" as its child
if (isBoxed(expr) && expectsUnboxed(parent, old)) {
return unbox(expr);
} else if (isUnboxed(expr) && expectsBoxed(parent, old)) {
return box(expr);
}
}
return n;
}
private boolean isUnboxed(Expr n) {
return !isBoxed(n);
}
protected boolean isBoxed(Expr n) {
if (n instanceof Field) {
return isBoxed((Field)n);
}
else if (n instanceof X10Call) {
return isBoxed((X10Call)n);
}
else if (n instanceof ClosureCall) {
return isBoxed((ClosureCall)n);
}
else if (n instanceof ParExpr) {
return isBoxed(((ParExpr)n).expr());
}
return isBoxedType(((Expr) n).type());
}
private boolean isBoxed(X10Call call) {
Type type = call.methodInstance().def().returnType().get();
// FIXME: inline & dispatch method conditions
if (isBoxedType(type))
return true;
return false;
}
private boolean isBoxed(ClosureCall call) {
// FIXME: these conditions are copied over from Emitter.printApplyMethodName() and X10PrettyPrinterVisitor.visit(ClosureCall)
Expr target = call.target();
if (target instanceof ParExpr) { target = ((ParExpr) target).expr(); }
boolean newClosure = target instanceof Closure_c;
MethodInstance mi = call.closureInstance();
if ((!newClosure && !mi.returnType().isVoid() && mi.formalTypes().size() == 0)
|| !(mi.returnType().isVoid() || (newClosure && !mi.returnType().isParameterType()))) {
// in this case generic apply$G is used
return true;
}
Type type = call.closureInstance().def().returnType().get();
if (isBoxedType(type))
return true;
return false;
}
private boolean isBoxed(Field field) {
if (isBoxedType(field.fieldInstance().def().type().get()))
return true;
return false;
}
private boolean expectsUnboxed(Node parent, Node n) {
// the majority of boxing decisions are made in codegen directly
/*
if (n instanceof Call && child instanceof Expr) {
return !expectsBoxed((Call) n, child);
}
else */
if (parent instanceof FieldAssign) {
// check type for the assignment rhs
if (n == ((FieldAssign)parent).right()) return !expectsBoxed((FieldAssign) parent);
// and return false for target object
return false;
}
else if (parent instanceof LocalAssign) {
if (n == ((LocalAssign)parent).right()) return !expectsBoxed((LocalAssign) parent);
return false;
}
else if (parent instanceof LocalDecl) {
if (n == ((LocalDecl)parent).init()) return !expectsBoxed((LocalDecl) parent);
return false;
}
else if (parent instanceof Eval) {
return false;
}
else if (parent instanceof Return) {
return !isBoxedType(returnType.peek());
}
else if (n instanceof Field) {
return !isBoxedType(((Field) n).type());
}
/*
// assume default rule: expect primitive types as unboxed, leave everything else as is
else if (n instanceof Expr) {
return !isBoxedType(((Expr) n).type());
}
*/
return false;
}
private boolean expectsBoxed(Node parent, Node n) {
// the majority of boxing is currently added by codegen
/*
if (n instanceof X10Call && child instanceof Expr) {
return expectsBoxed((X10Call) n, child);
}
else if (n instanceof X10New && child instanceof Expr) {
return expectsBoxed((X10New) n, child);
}
else if (n instanceof LocalDecl) {
return expectsBoxed((LocalDecl) n);
}
else */
if (parent instanceof FieldAssign) {
if (n == ((FieldAssign)parent).right()) return expectsBoxed((FieldAssign) parent);
return false;
}
else if (parent instanceof LocalAssign) {
if (parent == ((LocalAssign)parent).right()) return expectsBoxed((LocalAssign) parent);
return false;
}
else if (parent instanceof LocalDecl) {
if (n == ((LocalDecl)parent).init()) return expectsBoxed((LocalDecl) parent);
return false;
}
else if (parent instanceof Return) {
return isBoxedType(returnType.peek());
}
return false;
}
private boolean expectsBoxed(Call call, Node arg) {
// implicit this argument also is present
if (call.target() == arg)
return isBoxedType(call.target().type()); // targets of primitive types are unboxed
int i = call.arguments().indexOf(arg);
if (i < 0) throw new InternalCompilerError("BoxingPropagator: cannot find argument in call");
Type type = call.methodInstance().def().formalTypes().get(i).get();
return isBoxedType(type);
}
private boolean expectsBoxed(X10New call, Node arg) {
int i = call.arguments().indexOf(arg);
if (i < 0) throw new InternalCompilerError("BoxingPropagator: cannot find argument in new()");
if (i >= call.constructorInstance().def().formalTypes().size()) {
// a case when constructor's def() does not match the contructor call
// fall back to default rule -- a bug in earlier AST passes?
return isBoxedType(call.arguments().get(i).type());
}
Type type = call.constructorInstance().def().formalTypes().get(i).get();
return isBoxedType(type);
}
private boolean expectsBoxed(FieldAssign assign) {
return isBoxedType(assign.fieldInstance().def().type().get());
}
private boolean expectsBoxed(LocalAssign assign) {
return isBoxedType(assign.type());
}
private boolean expectsBoxed(LocalDecl decl) {
return isBoxedType(decl.declType());
}
private Node box(Expr expr) {
Position pos = Position.compilerGenerated(null);
return xnf.X10Cast(pos, xnf.CanonicalTypeNode(pos, expr.type()), expr, Converter.ConversionType.BOXING).type(expr.type());
}
private Node unbox(Expr expr) {
Position pos = Position.compilerGenerated(null);
return xnf.X10Cast(pos, xnf.CanonicalTypeNode(pos, expr.type()), expr, Converter.ConversionType.UNBOXING).type(expr.type());
}
private Node cast(Expr expr, Type type) {
Position pos = Position.COMPILER_GENERATED;
return xnf.X10Cast(pos, xnf.CanonicalTypeNode(pos, type), expr, Converter.ConversionType.PRIMITIVE).type(type);
}
private boolean isBoxedType(Type type) {
if (type == null) return false;
return X10PrettyPrinterVisitor.isBoxedType(type);
}
}