All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.ceylon.compiler.java.codegen.Operators Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/*
 * Copyright Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the authors tag. All rights reserved.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU General Public License version 2.
 * 
 * This particular file is subject to the "Classpath" exception as provided in the 
 * LICENSE file that accompanied this code.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License,
 * along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */

package com.redhat.ceylon.compiler.java.codegen;

import java.util.HashMap;
import java.util.Map;

import com.redhat.ceylon.compiler.java.codegen.AbstractTransformer.BoxingStrategy;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.AssignmentOp;
import com.redhat.ceylon.langtools.tools.javac.tree.JCTree;
import com.redhat.ceylon.model.typechecker.model.Type;

/**
 * Aggregation of mappings from ceylon operator to java operator and optimisation strategy.
 *
 * @author Stéphane Épardaud 
 */
public class Operators {

    private enum PrimitiveType {
        BOOLEAN("ceylon.language.Boolean"),
        BYTE("ceylon.language.Byte"),
        CHARACTER("ceylon.language.Character"),
        INTEGER("ceylon.language.Integer"),
        FLOAT("ceylon.language.Float"),
        STRING("ceylon.language.String");
        
        public final String fqn;
        
        PrimitiveType(String fqn) {
            this.fqn = fqn;
        }
    }

    private static final PrimitiveType[] IntegerByte = new PrimitiveType[]{PrimitiveType.INTEGER, PrimitiveType.BYTE};
    private static final PrimitiveType[] IntegerFloat = new PrimitiveType[]{PrimitiveType.INTEGER, PrimitiveType.FLOAT};
    private static final PrimitiveType[] IntegerFloatByte = new PrimitiveType[]{PrimitiveType.INTEGER, PrimitiveType.FLOAT, PrimitiveType.BYTE};
    private static final PrimitiveType[] IntegerCharacterByte = new PrimitiveType[]{PrimitiveType.INTEGER, PrimitiveType.CHARACTER, PrimitiveType.BYTE};
    private static final PrimitiveType[] IntegerFloatCharacter = new PrimitiveType[]{PrimitiveType.INTEGER, PrimitiveType.FLOAT, PrimitiveType.CHARACTER};
    private static final PrimitiveType[] IntegerFloatStringByte = new PrimitiveType[]{PrimitiveType.INTEGER, PrimitiveType.FLOAT, PrimitiveType.STRING, PrimitiveType.BYTE};
    private static final PrimitiveType[] All = PrimitiveType.values();

    public enum OptimisationStrategy {
        /** Optimize using the equivalent javac operator */
        OPTIMISE(true, false, BoxingStrategy.UNBOXED),
        /** Optimize using the static method (for a value type) */
        OPTIMISE_VALUE_TYPE(false, true, BoxingStrategy.UNBOXED),
        /** A special case used for String == */
        OPTIMISE_BOXING(false, false, BoxingStrategy.INDIFFERENT),
        /** No optimization possible: Call the (virtual) method corresponding to the ceylon operator */
        NONE(false, false, BoxingStrategy.BOXED);
        
        private BoxingStrategy boxingStrategy;
        private boolean useJavaOperator;
        private boolean useValueTypeMethod;

        OptimisationStrategy(boolean useJavaOperator, boolean useValueTypeMethod, BoxingStrategy boxingStrategy){
            this.useJavaOperator = useJavaOperator;
            this.useValueTypeMethod = useValueTypeMethod;
            this.boxingStrategy = boxingStrategy;
        }
        
        public BoxingStrategy getBoxingStrategy(){
            return boxingStrategy;
        }
        
        public boolean useJavaOperator(){
            return useJavaOperator;
        }
        
        public boolean useValueTypeMethod(){
            return useValueTypeMethod;
        }
    }
    
    //
    // Unary and binary operators
    
    public enum OperatorTranslation {
        
        // Unary operators
        UNARY_POSITIVE(Tree.PositiveOp.class, 1, "", JCTree.POS, IntegerFloatByte),
        UNARY_NEGATIVE(Tree.NegativeOp.class, 1, "negated", JCTree.NEG, IntegerFloatByte),
        
        UNARY_BITWISE_NOT(1, "not", JCTree.COMPL, IntegerByte),

        UNARY_POSTFIX_INCREMENT(Tree.PostfixIncrementOp.class, 1, "getSuccessor", JCTree.POSTINC, IntegerCharacterByte),
        UNARY_POSTFIX_DECREMENT(Tree.PostfixDecrementOp.class, 1, "getPredecessor", JCTree.POSTDEC, IntegerCharacterByte),
        UNARY_PREFIX_INCREMENT(Tree.IncrementOp.class, 1, "getSuccessor", JCTree.PREINC, IntegerCharacterByte),
        UNARY_PREFIX_DECREMENT(Tree.DecrementOp.class, 1, "getPredecessor", JCTree.PREDEC, IntegerCharacterByte),

        // Binary operators
        BINARY_SUM(Tree.SumOp.class, 2, "plus", JCTree.PLUS, IntegerFloatStringByte),
        BINARY_DIFFERENCE(Tree.DifferenceOp.class, 2, "minus", JCTree.MINUS, IntegerFloatByte),
        BINARY_PRODUCT(Tree.ProductOp.class, 2, "times", JCTree.MUL, IntegerFloat),
        BINARY_QUOTIENT(Tree.QuotientOp.class, 2, "divided", JCTree.DIV, IntegerFloat),
        BINARY_POWER(Tree.PowerOp.class, 2, "power", -1),
        BINARY_REMAINDER(Tree.RemainderOp.class, 2, "remainder", JCTree.MOD, PrimitiveType.INTEGER),
        
        BINARY_SCALE(Tree.ScaleOp.class, 2, "scale"),

        BINARY_BITWISE_AND(2, "and", JCTree.BITAND, IntegerByte),
        BINARY_BITWISE_OR(2, "or", JCTree.BITOR, IntegerByte),
        BINARY_BITWISE_XOR(2, "xor", JCTree.BITXOR, IntegerByte),
        BINARY_BITWISE_LOG_LEFT_SHIFT(2, "leftLogicalShift", JCTree.SL, IntegerByte),
        BINARY_BITWISE_LOG_RIGHT_SHIFT_INT(2, "rightLogicalShift", 0, JCTree.USR, PrimitiveType.INTEGER),
        BINARY_BITWISE_LOG_RIGHT_SHIFT_BYTE(2, "rightLogicalShift", 0xff, JCTree.USR, PrimitiveType.BYTE),
        BINARY_BITWISE_ARI_RIGHT_SHIFT(2, "rightArithmeticShift", JCTree.SR, IntegerByte),

        BINARY_AND(Tree.AndOp.class, 2, "", JCTree.AND, PrimitiveType.BOOLEAN),
        BINARY_OR(Tree.OrOp.class, 2, "", JCTree.OR, PrimitiveType.BOOLEAN),

        BINARY_UNION(Tree.UnionOp.class, 2, "union"),
        BINARY_INTERSECTION(Tree.IntersectionOp.class, 2, "intersection"),
        BINARY_COMPLEMENT(Tree.ComplementOp.class, 2, "complement"), 
        
        BINARY_EQUAL(Tree.EqualOp.class, 2, "equals", JCTree.EQ, All){
            @Override
            public OptimisationStrategy getBinOpOptimisationStrategy(Tree.Term t, 
                    Tree.Term leftTerm, Type leftType, 
                    Tree.Term rightTerm, Type rightType, AbstractTransformer gen) {
                // no optimised operator returns a boxed type 
                if(!t.getUnboxed())
                    return OptimisationStrategy.NONE;
                OptimisationStrategy left = isTermOptimisable(leftTerm, gen);
                OptimisationStrategy right = isTermOptimisable(rightTerm, gen);
                if(left == OptimisationStrategy.OPTIMISE
                        && right == OptimisationStrategy.OPTIMISE){
                    // make sure both types are the same, can't optimise otherwise
                    if(!leftType.isExactly(rightType))
                        return OptimisationStrategy.NONE;
                    
                    // special case for String where we can't use == but don't need to box
                    if(gen.isCeylonString(leftType)
                        && gen.isCeylonString(rightType)){
                        return OptimisationStrategy.OPTIMISE_BOXING;
                    }
                }
                return lessPermissive(left, right);
            }
        },
        BINARY_COMPARE(Tree.CompareOp.class, 2, "compare"),

        // Binary operators that act on intermediary Comparison objects
        BINARY_LARGER(Tree.LargerOp.class, 2, JCTree.EQ, "larger", JCTree.GT, IntegerFloatCharacter),
        BINARY_SMALLER(Tree.SmallerOp.class, 2, JCTree.EQ, "smaller", JCTree.LT, IntegerFloatCharacter),
        BINARY_LARGE_AS(Tree.LargeAsOp.class, 2, JCTree.NE, "smaller",  JCTree.GE, IntegerFloatCharacter),
        BINARY_SMALL_AS(Tree.SmallAsOp.class, 2, JCTree.NE, "larger", JCTree.LE, IntegerFloatCharacter);

        // we can have either a mapping from Tree operator class
        Class operatorClass;
        
        int arity;
        /** The name of the method which the boxed transformation invokes */
        String ceylonMethod;
        int javacOperator;
        PrimitiveType[] optimisableTypes;
        /** The name of a top level value from the language module */
        String ceylonValue;
        /** The operator with which to compare against {@link #ceylonValue} */
        int javacValueOperator;
        /** A mask to apply to the LHS before applying the operator */
        int valueMask;
        
        OperatorTranslation(int arity, String ceylonMethod, 
                int javacOperator, PrimitiveType... optimisableTypes) {
            this.ceylonMethod = ceylonMethod;
            this.javacOperator = javacOperator;
            this.optimisableTypes = optimisableTypes;
            this.arity = arity;
        }
        OperatorTranslation(int arity, String ceylonMethod, int valueMask,
                int javacOperator, PrimitiveType... optimisableTypes) {
            this(arity, ceylonMethod, javacOperator, optimisableTypes);
            this.valueMask = valueMask;
        }
        OperatorTranslation(Class operatorClass, 
                int arity, String ceylonMethod, 
                int javacOperator, PrimitiveType... optimisableTypes) {
            this(arity, ceylonMethod, javacOperator, optimisableTypes);
            this.operatorClass = operatorClass;
        }
        OperatorTranslation(Class operatorClass, 
                int arity, int javacOperator1, String ceylonValue, 
                int javacOperator, PrimitiveType... optimisableTypes) {
            this.ceylonValue = ceylonValue;
            this.javacValueOperator = javacOperator1;
            this.javacOperator = javacOperator;
            this.optimisableTypes = optimisableTypes;
            this.arity = arity;
            this.operatorClass = operatorClass;
        }
        OperatorTranslation(Class operatorClass, 
                int arity, String ceylonMethod) {
            this(operatorClass, arity, ceylonMethod, -1);
        }
        
        public final OptimisationStrategy getUnOpOptimisationStrategy(Tree.Term expression, Tree.Term term, AbstractTransformer gen){
            // no optimised operator returns a boxed type 
            if(!expression.getUnboxed())
                return OptimisationStrategy.NONE;
            return isTermOptimisable(term, gen);
        }
        
        public OptimisationStrategy getBinOpOptimisationStrategy(Tree.Term expression, 
                Tree.Term leftTerm, 
                Tree.Term rightTerm, AbstractTransformer gen){
            return getBinOpOptimisationStrategy(expression, leftTerm, leftTerm.getTypeModel(),
                    rightTerm, rightTerm.getTypeModel(), gen);
        }
        
        public OptimisationStrategy getBinOpOptimisationStrategy(Tree.Term expression, 
                Tree.Term leftTerm, Type leftType, 
                Tree.Term rightTerm, Type rightType, AbstractTransformer gen){
            // no optimised operator returns a boxed type 
            if(!expression.getUnboxed())
                return OptimisationStrategy.NONE;
            // Can we do an operator optimization?
            OptimisationStrategy optimisationStrategy;
            OptimisationStrategy left = isTermOptimisable(leftTerm, leftType, gen);
            OptimisationStrategy right = isTermOptimisable(rightTerm, rightType, gen);
            optimisationStrategy = mostAggressive(left, right);
            if (optimisationStrategy != OptimisationStrategy.OPTIMISE) {
                // we can't use operator optimization, but maybe was can
                // use static value type method to avoid boxing
                if (Decl.isValueTypeDecl(leftType)) {
                    optimisationStrategy = OptimisationStrategy.OPTIMISE_VALUE_TYPE;
                } else if (leftType.getDeclaration().getSelfType() != null
                        && Decl.isValueTypeDecl(leftType.getTypeArguments().get(leftType.getDeclaration().getSelfType().getDeclaration()))) {
                    // a self type of a value type (e.g. Summable)
                    optimisationStrategy = OptimisationStrategy.OPTIMISE_VALUE_TYPE;
                }
            }
            return optimisationStrategy;
        }
        
        /** Returns the least aggressive optimization of the two given optimizations */
        protected static OptimisationStrategy lessPermissive(OptimisationStrategy left, OptimisationStrategy right) {
            // it's from most permissive to less permissive, so return the one with the higher ordinal (less permissive)
            if(left.ordinal() > right.ordinal())
                return left;
            return right;
        }
        /** Returns the more aggressive optimization of the two given optimizations */
        protected static OptimisationStrategy mostAggressive(OptimisationStrategy left, OptimisationStrategy right) {
            // it's from most permissive to less permissive, so return the one with the higher ordinal (less permissive)
            if(left.ordinal() < right.ordinal())
                return left;
            return right;
        }
        final OptimisationStrategy isTermOptimisable(Tree.Term t, AbstractTransformer gen){
            return isTermOptimisable(t, t.getTypeModel(), gen);
        }
        final OptimisationStrategy isTermOptimisable(Tree.Term t, Type pt, AbstractTransformer gen){
            if(javacOperator < 0 || !t.getUnboxed())
                return OptimisationStrategy.NONE;
            if(pt == null) // typechecker error?
                return OptimisationStrategy.NONE;
            if(optimisableTypes == null)
                return OptimisationStrategy.NONE;
            // see if it's a supported type
            for(PrimitiveType type : optimisableTypes){
                switch(type){
                case BOOLEAN:
                    if(gen.isCeylonBoolean(pt))
                        return OptimisationStrategy.OPTIMISE;
                    break;
                case BYTE:
                    if(gen.isCeylonByte(pt))
                        return OptimisationStrategy.OPTIMISE;
                    break;
                case CHARACTER:
                    if(gen.isCeylonCharacter(pt))
                        return OptimisationStrategy.OPTIMISE;
                    break;
                case FLOAT:
                    if(gen.isCeylonFloat(pt))
                        return OptimisationStrategy.OPTIMISE;
                    break;
                case INTEGER:
                    if(gen.isCeylonInteger(pt))
                        return OptimisationStrategy.OPTIMISE;
                    break;
                case STRING:
                    if(gen.isCeylonString(pt))
                        return OptimisationStrategy.OPTIMISE;
                    break;
                }
            }
            return OptimisationStrategy.NONE;
        }
        
        public int getArity(){
            return arity;
        }
    }
    
    //
    // Assignment operators
    
    public enum AssignmentOperatorTranslation {
        // Assignment operators
        ADD(Tree.AddAssignOp.class, OperatorTranslation.BINARY_SUM, JCTree.PLUS_ASG),
        SUBSTRACT(Tree.SubtractAssignOp.class, OperatorTranslation.BINARY_DIFFERENCE, JCTree.MINUS_ASG),
        MULTIPLY(Tree.MultiplyAssignOp.class, OperatorTranslation.BINARY_PRODUCT, JCTree.MUL_ASG),
        DIVIDE(Tree.DivideAssignOp.class, OperatorTranslation.BINARY_QUOTIENT, JCTree.DIV_ASG),
        REMAINDER(Tree.RemainderAssignOp.class, OperatorTranslation.BINARY_REMAINDER, JCTree.MOD_ASG),
        AND(Tree.AndAssignOp.class, OperatorTranslation.BINARY_AND, JCTree.BITAND_ASG),
        OR(Tree.OrAssignOp.class, OperatorTranslation.BINARY_OR, JCTree.BITOR_ASG),
        
        // Set assignment
        BINARY_UNION(Tree.UnionAssignOp.class, OperatorTranslation.BINARY_UNION),
        BINARY_INTERSECTION(Tree.IntersectAssignOp.class, OperatorTranslation.BINARY_INTERSECTION),
        BINARY_COMPLEMENT(Tree.ComplementAssignOp.class, OperatorTranslation.BINARY_COMPLEMENT),
        ;
        
        int javacOperator;
        OperatorTranslation binaryOperator;
        Class operatorClass;
        
        AssignmentOperatorTranslation(Class operatorClass, OperatorTranslation binaryOperator) {
            this.operatorClass = operatorClass;
            this.binaryOperator = binaryOperator;
        }

        AssignmentOperatorTranslation(Class operatorClass,
                OperatorTranslation binaryOperator,
                int javacOperator) {
            this.operatorClass = operatorClass;
            this.javacOperator = javacOperator;
            this.binaryOperator = binaryOperator;
        }
    }

    //
    // Public API
    
    private static final Map, OperatorTranslation> operators;
    private static final Map methodsAsOperators;
    private static final Map, AssignmentOperatorTranslation> assignmentOperators;

    static {
        operators = new HashMap, OperatorTranslation>();
        methodsAsOperators = new HashMap();
        assignmentOperators = new HashMap, AssignmentOperatorTranslation>();
        
        for(OperatorTranslation operator : OperatorTranslation.values()){
            // some operators are virtual translations from method calls to java operators and so don't have
            // a Tree class
            if(operator.operatorClass != null) {
                operators.put(operator.operatorClass, operator);
            } else {
                for (PrimitiveType t : operator.optimisableTypes) {
                    String optimisedMethod = t.fqn + "." + operator.ceylonMethod;
                    methodsAsOperators.put(optimisedMethod , operator);
                }
            }
        }

        for(AssignmentOperatorTranslation operator : AssignmentOperatorTranslation.values())
            assignmentOperators.put(operator.operatorClass, operator);
    }

    public static OperatorTranslation getOperator(Class operatorClass) {
        return operators.get(operatorClass);
    }

    public static OperatorTranslation getOperator(String signature) {
        return methodsAsOperators.get(signature);
    }

    public static AssignmentOperatorTranslation getAssignmentOperator(Class operatorClass) {
        return assignmentOperators.get(operatorClass);
    }
    
    // only there to make sure this class is initialised before the enums defined in it, otherwise we
    // get an initialisation error
    public static void init(){}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy