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.base.Predicate;
020 import com.google.common.collect.Collections2;
021 import com.intellij.openapi.util.Pair;
022 import kotlin.collections.CollectionsKt;
023 import kotlin.jvm.functions.Function1;
024 import org.jetbrains.annotations.NotNull;
025 import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer;
026 import org.jetbrains.org.objectweb.asm.Opcodes;
027 import org.jetbrains.org.objectweb.asm.Type;
028 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
029 import org.jetbrains.org.objectweb.asm.tree.*;
030 import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
031 import org.jetbrains.org.objectweb.asm.tree.analysis.Frame;
032
033 import java.util.*;
034
035 public class RedundantBoxingMethodTransformer extends MethodTransformer {
036
037 @Override
038 public void transform(@NotNull String internalClassName, @NotNull MethodNode node) {
039 RedundantBoxingInterpreter interpreter = new RedundantBoxingInterpreter(node.instructions);
040 Frame<BasicValue>[] frames = analyze(
041 internalClassName, node, interpreter
042 );
043 interpretPopInstructionsForBoxedValues(interpreter, node, frames);
044
045 RedundantBoxedValuesCollection valuesToOptimize = interpreter.getCandidatesBoxedValues();
046
047 if (!valuesToOptimize.isEmpty()) {
048 // has side effect on valuesToOptimize and frames, containing BoxedBasicValues that are unsafe to remove
049 removeValuesClashingWithVariables(valuesToOptimize, node, frames);
050
051 adaptLocalVariableTableForBoxedValues(node, frames);
052
053 applyVariablesRemapping(node, buildVariablesRemapping(valuesToOptimize, node));
054
055 adaptInstructionsForBoxedValues(node, valuesToOptimize);
056 }
057 }
058
059 private static void interpretPopInstructionsForBoxedValues(
060 @NotNull RedundantBoxingInterpreter interpreter,
061 @NotNull MethodNode node,
062 @NotNull Frame<BasicValue>[] frames
063 ) {
064 for (int i = 0; i < node.instructions.size(); i++) {
065 AbstractInsnNode insn = node.instructions.get(i);
066 if ((insn.getOpcode() != Opcodes.POP && insn.getOpcode() != Opcodes.POP2) || frames[i] == null) {
067 continue;
068 }
069
070 BasicValue top = frames[i].getStack(frames[i].getStackSize() - 1);
071 interpreter.processPopInstruction(insn, top);
072
073 if (top.getSize() == 1 && insn.getOpcode() == Opcodes.POP2) {
074 interpreter.processPopInstruction(insn, frames[i].getStack(frames[i].getStackSize() - 2));
075 }
076 }
077 }
078
079 private static void removeValuesClashingWithVariables(
080 @NotNull RedundantBoxedValuesCollection values,
081 @NotNull MethodNode node,
082 @NotNull Frame<BasicValue>[] frames
083 ) {
084 while (removeValuesClashingWithVariablesPass(values, node, frames)) {
085 // do nothing
086 }
087 }
088
089 private static boolean removeValuesClashingWithVariablesPass(
090 @NotNull RedundantBoxedValuesCollection values,
091 @NotNull MethodNode node,
092 @NotNull Frame<BasicValue>[] frames
093 ) {
094 boolean needToRepeat = false;
095
096 for (LocalVariableNode localVariableNode : node.localVariables) {
097 if (Type.getType(localVariableNode.desc).getSort() != Type.OBJECT) {
098 continue;
099 }
100
101 List<BasicValue> usedValues = getValuesStoredOrLoadedToVariable(localVariableNode, node, frames);
102
103 Collection<BasicValue> boxed = Collections2.filter(usedValues, new Predicate<BasicValue>() {
104 @Override
105 public boolean apply(BasicValue input) {
106 return input instanceof BoxedBasicValue;
107 }
108 });
109
110 if (boxed.isEmpty()) continue;
111
112 final BoxedBasicValue firstBoxed = (BoxedBasicValue) boxed.iterator().next();
113
114 if (CollectionsKt.any(usedValues, new Function1<BasicValue, Boolean>() {
115 @Override
116 public Boolean invoke(BasicValue input) {
117 if (input == BasicValue.UNINITIALIZED_VALUE) return false;
118 return input == null ||
119 !(input instanceof BoxedBasicValue) ||
120 !((BoxedBasicValue) input).isSafeToRemove() ||
121 !((BoxedBasicValue) input).getPrimitiveType().equals(firstBoxed.getPrimitiveType());
122 }
123 })) {
124 for (BasicValue value : usedValues) {
125 if (value instanceof BoxedBasicValue && ((BoxedBasicValue) value).isSafeToRemove()) {
126 values.remove((BoxedBasicValue) value);
127 needToRepeat = true;
128 }
129 }
130 }
131 }
132
133 return needToRepeat;
134 }
135
136 private static void adaptLocalVariableTableForBoxedValues(@NotNull MethodNode node, @NotNull Frame<BasicValue>[] frames) {
137 for (LocalVariableNode localVariableNode : node.localVariables) {
138 if (Type.getType(localVariableNode.desc).getSort() != Type.OBJECT) {
139 continue;
140 }
141
142 for (BasicValue value : getValuesStoredOrLoadedToVariable(localVariableNode, node, frames)) {
143 if (value == null || !(value instanceof BoxedBasicValue) || !((BoxedBasicValue) value).isSafeToRemove()) continue;
144 localVariableNode.desc = ((BoxedBasicValue) value).getPrimitiveType().getDescriptor();
145 }
146 }
147 }
148
149 @NotNull
150 private static List<BasicValue> getValuesStoredOrLoadedToVariable(
151 @NotNull LocalVariableNode localVariableNode,
152 @NotNull MethodNode node,
153 @NotNull Frame<BasicValue>[] frames
154 ) {
155 List<BasicValue> values = new ArrayList<BasicValue>();
156 InsnList insnList = node.instructions;
157 int from = insnList.indexOf(localVariableNode.start) + 1;
158 int to = insnList.indexOf(localVariableNode.end) - 1;
159
160 Frame<BasicValue> frameForFromInstr = frames[from];
161 if (frameForFromInstr != null) {
162 BasicValue localVarValue = frameForFromInstr.getLocal(localVariableNode.index);
163 if (localVarValue != null) {
164 values.add(localVarValue);
165 }
166 }
167
168 for (int i = from; i <= to; i++) {
169 if (i < 0 || i >= insnList.size()) continue;
170
171 AbstractInsnNode insn = insnList.get(i);
172 if ((insn.getOpcode() == Opcodes.ASTORE || insn.getOpcode() == Opcodes.ALOAD) &&
173 ((VarInsnNode) insn).var == localVariableNode.index) {
174
175 if (frames[i] == null) {
176 //unreachable code
177 continue;
178 }
179
180 if (insn.getOpcode() == Opcodes.ASTORE) {
181 values.add(frames[i].getStack(frames[i].getStackSize() - 1));
182 }
183 else {
184 values.add(frames[i].getLocal(((VarInsnNode) insn).var));
185 }
186 }
187 }
188
189 return values;
190 }
191
192 @NotNull
193 private static int[] buildVariablesRemapping(@NotNull RedundantBoxedValuesCollection values, @NotNull MethodNode node) {
194 Set<Integer> doubleSizedVars = new HashSet<Integer>();
195 for (BoxedBasicValue value : values) {
196 if (value.getPrimitiveType().getSize() == 2) {
197 doubleSizedVars.addAll(value.getVariablesIndexes());
198 }
199 }
200
201 node.maxLocals += doubleSizedVars.size();
202 int[] remapping = new int[node.maxLocals];
203 for (int i = 0; i < remapping.length; i++) {
204 remapping[i] = i;
205 }
206
207 for (int varIndex : doubleSizedVars) {
208 for (int i = varIndex + 1; i < remapping.length; i++) {
209 remapping[i]++;
210 }
211 }
212
213 return remapping;
214 }
215
216 private static void applyVariablesRemapping(@NotNull MethodNode node, @NotNull int[] remapping) {
217 for (AbstractInsnNode insn : node.instructions.toArray()) {
218 if (insn instanceof VarInsnNode) {
219 ((VarInsnNode) insn).var = remapping[((VarInsnNode) insn).var];
220 }
221 if (insn instanceof IincInsnNode) {
222 ((IincInsnNode) insn).var = remapping[((IincInsnNode) insn).var];
223 }
224 }
225
226 for (LocalVariableNode localVariableNode : node.localVariables) {
227 localVariableNode.index = remapping[localVariableNode.index];
228 }
229 }
230
231 private static void adaptInstructionsForBoxedValues(
232 @NotNull MethodNode node,
233 @NotNull RedundantBoxedValuesCollection values
234 ) {
235 for (BoxedBasicValue value : values) {
236 adaptInstructionsForBoxedValue(node, value);
237 }
238 }
239
240 private static void adaptInstructionsForBoxedValue(@NotNull MethodNode node, @NotNull BoxedBasicValue value) {
241 adaptBoxingInstruction(node, value);
242
243 for (Pair<AbstractInsnNode, Type> cast : value.getUnboxingWithCastInsns()) {
244 adaptCastInstruction(node, value, cast);
245 }
246
247 for (AbstractInsnNode insn : value.getAssociatedInsns()) {
248 adaptInstruction(node, insn, value);
249 }
250 }
251
252 private static void adaptBoxingInstruction(@NotNull MethodNode node, @NotNull BoxedBasicValue value) {
253 if (!value.isFromProgressionIterator()) {
254 node.instructions.remove(value.getBoxingInsn());
255 }
256 else {
257 ProgressionIteratorBasicValue iterator = value.getProgressionIterator();
258 assert iterator != null : "iterator should not be null because isFromProgressionIterator returns true";
259
260 //add checkcast to kotlin/<T>Iterator before next() call
261 node.instructions.insertBefore(
262 value.getBoxingInsn(),
263 new TypeInsnNode(Opcodes.CHECKCAST, iterator.getType().getInternalName())
264 );
265
266 //invoke concrete method (kotlin/<T>iterator.next<T>())
267 node.instructions.set(
268 value.getBoxingInsn(),
269 new MethodInsnNode(
270 Opcodes.INVOKEVIRTUAL,
271 iterator.getType().getInternalName(),
272 iterator.getNextMethodName(),
273 iterator.getNextMethodDesc(),
274 false
275 )
276 );
277 }
278 }
279
280 private static void adaptCastInstruction(
281 @NotNull MethodNode node,
282 @NotNull BoxedBasicValue value,
283 @NotNull Pair<AbstractInsnNode, Type> castWithType
284 ) {
285 AbstractInsnNode castInsn = castWithType.getFirst();
286 MethodNode castInsnsListener = new MethodNode(Opcodes.ASM5);
287 new InstructionAdapter(castInsnsListener).cast(value.getPrimitiveType(), castWithType.getSecond());
288
289 for (AbstractInsnNode insn : castInsnsListener.instructions.toArray()) {
290 node.instructions.insertBefore(castInsn, insn);
291 }
292
293 node.instructions.remove(castInsn);
294 }
295
296 private static void adaptInstruction(
297 @NotNull MethodNode node, @NotNull AbstractInsnNode insn, @NotNull BoxedBasicValue value
298 ) {
299 boolean isDoubleSize = value.isDoubleSize();
300
301 switch (insn.getOpcode()) {
302 case Opcodes.POP:
303 if (isDoubleSize) {
304 node.instructions.set(
305 insn,
306 new InsnNode(Opcodes.POP2)
307 );
308 }
309 break;
310 case Opcodes.DUP:
311 if (isDoubleSize) {
312 node.instructions.set(
313 insn,
314 new InsnNode(Opcodes.DUP2)
315 );
316 }
317 break;
318 case Opcodes.ASTORE:
319 case Opcodes.ALOAD:
320 int intVarOpcode = insn.getOpcode() == Opcodes.ASTORE ? Opcodes.ISTORE : Opcodes.ILOAD;
321 node.instructions.set(
322 insn,
323 new VarInsnNode(
324 value.getPrimitiveType().getOpcode(intVarOpcode),
325 ((VarInsnNode) insn).var
326 )
327 );
328 break;
329 case Opcodes.INSTANCEOF:
330 node.instructions.insertBefore(
331 insn,
332 new InsnNode(isDoubleSize ? Opcodes.POP2 : Opcodes.POP)
333 );
334 node.instructions.set(insn, new InsnNode(Opcodes.ICONST_1));
335 break;
336 default:
337 // CHECKCAST or unboxing-method call
338 node.instructions.remove(insn);
339 }
340 }
341 }