001    /*
002     * Copyright 2010-2015 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.codegen.optimization.boxing;
018    
019    import com.google.common.collect.ImmutableSet;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.kotlin.codegen.AsmUtil;
023    import org.jetbrains.kotlin.codegen.RangeCodegenUtil;
024    import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter;
025    import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType;
026    import org.jetbrains.kotlin.name.FqName;
027    import org.jetbrains.kotlin.builtins.PrimitiveType;
028    import org.jetbrains.org.objectweb.asm.Opcodes;
029    import org.jetbrains.org.objectweb.asm.Type;
030    import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
031    import org.jetbrains.org.objectweb.asm.tree.InsnList;
032    import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode;
033    import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
034    import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
035    
036    import java.util.HashMap;
037    import java.util.List;
038    import java.util.Map;
039    
040    public class BoxingInterpreter extends OptimizationBasicInterpreter {
041        private static final ImmutableSet<String> UNBOXING_METHOD_NAMES;
042    
043        static {
044            UNBOXING_METHOD_NAMES = ImmutableSet.of(
045                    "booleanValue", "charValue", "byteValue", "shortValue", "intValue", "floatValue", "longValue", "doubleValue"
046            );
047        }
048    
049    
050        private final Map<Integer, BoxedBasicValue> boxingPlaces = new HashMap<Integer, BoxedBasicValue>();
051        private final InsnList insnList;
052    
053        public BoxingInterpreter(InsnList insnList) {
054            this.insnList = insnList;
055        }
056    
057        @NotNull
058        protected BasicValue createNewBoxing(
059                @NotNull AbstractInsnNode insn, @NotNull Type type,
060                @Nullable ProgressionIteratorBasicValue progressionIterator
061        ) {
062            int index = insnList.indexOf(insn);
063            if (!boxingPlaces.containsKey(index)) {
064                BoxedBasicValue boxedBasicValue = new BoxedBasicValue(type, insn, progressionIterator);
065                onNewBoxedValue(boxedBasicValue);
066                boxingPlaces.put(index, boxedBasicValue);
067            }
068    
069            return boxingPlaces.get(index);
070        }
071    
072        @Override
073        @Nullable
074        public BasicValue naryOperation(@NotNull AbstractInsnNode insn, @NotNull List<? extends BasicValue> values) throws AnalyzerException {
075            BasicValue value = super.naryOperation(insn, values);
076    
077            if (values.isEmpty()) return value;
078    
079            BasicValue firstArg = values.get(0);
080    
081            if (isBoxing(insn)) {
082                return createNewBoxing(insn, value.getType(), null);
083            }
084            else if (isUnboxing(insn) &&
085                     firstArg instanceof BoxedBasicValue) {
086                onUnboxing(insn, (BoxedBasicValue) firstArg, value.getType());
087            }
088            else if (isIteratorMethodCallOfProgression(insn, values)) {
089                return new ProgressionIteratorBasicValue(
090                        getValuesTypeOfProgressionClass(firstArg.getType().getInternalName())
091                );
092            }
093            else if (isNextMethodCallOfProgressionIterator(insn, values)) {
094                assert firstArg instanceof ProgressionIteratorBasicValue : "firstArg should be progression iterator";
095    
096                ProgressionIteratorBasicValue progressionIterator = (ProgressionIteratorBasicValue) firstArg;
097                return createNewBoxing(
098                        insn,
099                        AsmUtil.boxType(progressionIterator.getValuesPrimitiveType()),
100                        progressionIterator
101                );
102            }
103            else {
104                // nary operation should be a method call or multinewarray
105                // arguments for multinewarray could be only numeric
106                // so if there are boxed values in args, it's not a case of multinewarray
107                for (BasicValue arg : values) {
108                    if (arg instanceof BoxedBasicValue) {
109                        onMethodCallWithBoxedValue((BoxedBasicValue) arg);
110                    }
111                }
112            }
113    
114            return value;
115        }
116    
117        private static boolean isWrapperClassNameOrNumber(@NotNull String internalClassName) {
118            return isWrapperClassName(internalClassName) || internalClassName.equals(Type.getInternalName(Number.class));
119        }
120    
121        private static boolean isWrapperClassName(@NotNull String internalClassName) {
122            return JvmPrimitiveType.isWrapperClassName(
123                    buildFqNameByInternal(internalClassName)
124            );
125        }
126    
127        @NotNull
128        private static FqName buildFqNameByInternal(@NotNull String internalClassName) {
129            return new FqName(Type.getObjectType(internalClassName).getClassName());
130        }
131    
132        private static boolean isUnboxing(@NotNull AbstractInsnNode insn) {
133            if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL) return false;
134    
135            MethodInsnNode methodInsn = (MethodInsnNode) insn;
136    
137            return isWrapperClassNameOrNumber(methodInsn.owner) && isUnboxingMethodName(methodInsn.name);
138        }
139    
140        private static boolean isUnboxingMethodName(@NotNull String name) {
141            return UNBOXING_METHOD_NAMES.contains(name);
142        }
143    
144        private static boolean isBoxing(@NotNull AbstractInsnNode insn) {
145            if (insn.getOpcode() != Opcodes.INVOKESTATIC) return false;
146    
147            MethodInsnNode node = (MethodInsnNode) insn;
148    
149            return isWrapperClassName(node.owner) && "valueOf".equals(node.name) &&
150                   Type.getMethodDescriptor(
151                           Type.getObjectType(node.owner),
152                           AsmUtil.unboxType(Type.getObjectType(node.owner))
153                   ).equals(node.desc);
154        }
155    
156        private static boolean isNextMethodCallOfProgressionIterator(
157                @NotNull AbstractInsnNode insn, @NotNull List<? extends BasicValue> values
158        ) {
159            return (insn.getOpcode() == Opcodes.INVOKEINTERFACE &&
160                    values.get(0) instanceof ProgressionIteratorBasicValue &&
161                    "next".equals(((MethodInsnNode) insn).name));
162        }
163    
164        private static boolean isIteratorMethodCallOfProgression(
165                @NotNull AbstractInsnNode insn, @NotNull List<? extends BasicValue> values
166        ) {
167            return (insn.getOpcode() == Opcodes.INVOKEINTERFACE &&
168                    values.get(0).getType() != null &&
169                    isProgressionClass(values.get(0).getType().getInternalName()) &&
170                    "iterator".equals(((MethodInsnNode) insn).name));
171        }
172    
173        private static boolean isProgressionClass(String internalClassName) {
174            return RangeCodegenUtil.isRangeOrProgression(buildFqNameByInternal(internalClassName));
175        }
176    
177        /**
178         * e.g. for "kotlin/IntRange" it returns "Int"
179         *
180         * @param progressionClassInternalName
181         * @return
182         * @throws java.lang.AssertionError if progressionClassInternalName is not progression class internal name
183         */
184        @NotNull
185        private static String getValuesTypeOfProgressionClass(String progressionClassInternalName) {
186            PrimitiveType type = RangeCodegenUtil.getPrimitiveRangeOrProgressionElementType(
187                    buildFqNameByInternal(progressionClassInternalName)
188            );
189    
190            assert type != null : "type should be not null";
191    
192            return type.getTypeName().asString();
193        }
194    
195        @Override
196        public BasicValue unaryOperation(@NotNull AbstractInsnNode insn, @NotNull BasicValue value) throws AnalyzerException {
197            if (insn.getOpcode() == Opcodes.CHECKCAST && isExactValue(value)) {
198                return value;
199            }
200    
201            return super.unaryOperation(insn, value);
202        }
203    
204        protected boolean isExactValue(@NotNull BasicValue value) {
205            return value instanceof ProgressionIteratorBasicValue ||
206                   value instanceof BoxedBasicValue ||
207                   (value.getType() != null && isProgressionClass(value.getType().getInternalName()));
208        }
209    
210        @Override
211        @NotNull
212        public BasicValue merge(@NotNull BasicValue v, @NotNull BasicValue w) {
213            if (v instanceof BoxedBasicValue && ((BoxedBasicValue) v).typeEquals(w)) {
214                onMergeSuccess((BoxedBasicValue) v, (BoxedBasicValue) w);
215                return v;
216            }
217    
218            if (v instanceof BoxedBasicValue) {
219                onMergeFail((BoxedBasicValue) v);
220            }
221    
222            if (w instanceof BoxedBasicValue) {
223                onMergeFail((BoxedBasicValue) w);
224            }
225    
226            return super.merge(v, w);
227        }
228    
229        protected void onNewBoxedValue(@NotNull BoxedBasicValue value) {
230    
231        }
232    
233        protected void onUnboxing(@NotNull AbstractInsnNode insn, @NotNull BoxedBasicValue value, @NotNull Type resultType) {
234    
235        }
236    
237        protected void onMethodCallWithBoxedValue(@NotNull BoxedBasicValue value) {
238    
239        }
240    
241        protected void onMergeFail(@NotNull BoxedBasicValue value) {
242    
243        }
244    
245        protected void onMergeSuccess(@NotNull BoxedBasicValue v, @NotNull BoxedBasicValue w) {
246    
247        }
248    }