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