001    /*
002    * Copyright 2010-2013 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.jet.codegen.inline;
018    
019    import com.intellij.openapi.vfs.VirtualFile;
020    import com.intellij.psi.PsiElement;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.jet.codegen.*;
024    import org.jetbrains.jet.codegen.binding.CodegenBinding;
025    import org.jetbrains.jet.codegen.context.CodegenContext;
026    import org.jetbrains.jet.codegen.context.MethodContext;
027    import org.jetbrains.jet.codegen.context.PackageContext;
028    import org.jetbrains.jet.codegen.state.GenerationState;
029    import org.jetbrains.jet.codegen.state.JetTypeMapper;
030    import org.jetbrains.jet.descriptors.serialization.descriptors.DeserializedSimpleFunctionDescriptor;
031    import org.jetbrains.jet.lang.descriptors.*;
032    import org.jetbrains.jet.lang.descriptors.impl.AnonymousFunctionDescriptor;
033    import org.jetbrains.jet.lang.psi.*;
034    import org.jetbrains.jet.lang.resolve.BindingContext;
035    import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils;
036    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
037    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
038    import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants;
039    import org.jetbrains.jet.lang.resolve.java.jvmSignature.JvmMethodParameterKind;
040    import org.jetbrains.jet.lang.resolve.java.jvmSignature.JvmMethodParameterSignature;
041    import org.jetbrains.jet.lang.resolve.java.jvmSignature.JvmMethodSignature;
042    import org.jetbrains.jet.lang.types.lang.InlineStrategy;
043    import org.jetbrains.jet.lang.types.lang.InlineUtil;
044    import org.jetbrains.jet.renderer.DescriptorRenderer;
045    import org.jetbrains.org.objectweb.asm.MethodVisitor;
046    import org.jetbrains.org.objectweb.asm.Opcodes;
047    import org.jetbrains.org.objectweb.asm.Type;
048    import org.jetbrains.org.objectweb.asm.commons.Method;
049    import org.jetbrains.org.objectweb.asm.tree.MethodNode;
050    
051    import java.io.IOException;
052    import java.util.HashMap;
053    import java.util.List;
054    import java.util.ListIterator;
055    import java.util.Map;
056    
057    import static org.jetbrains.jet.codegen.AsmUtil.*;
058    import static org.jetbrains.jet.codegen.inline.InlineCodegenUtil.addInlineMarker;
059    
060    public class InlineCodegen implements CallGenerator {
061        private final GenerationState state;
062        private final JetTypeMapper typeMapper;
063        private final BindingContext bindingContext;
064    
065        private final SimpleFunctionDescriptor functionDescriptor;
066        private final JvmMethodSignature jvmSignature;
067        private final JetElement callElement;
068        private final MethodContext context;
069        private final ExpressionCodegen codegen;
070    
071        private final boolean asFunctionInline;
072        private final int initialFrameSize;
073        private final boolean isSameModule;
074    
075        protected final ParametersBuilder invocationParamBuilder = ParametersBuilder.newBuilder();
076        protected final Map<Integer, LambdaInfo> expressionMap = new HashMap<Integer, LambdaInfo>();
077    
078        private LambdaInfo activeLambda;
079    
080        public InlineCodegen(
081                @NotNull ExpressionCodegen codegen,
082                @NotNull GenerationState state,
083                @NotNull SimpleFunctionDescriptor functionDescriptor,
084                @NotNull JetElement callElement
085        ) {
086            assert functionDescriptor.getInlineStrategy().isInline() : "InlineCodegen could inline only inline function but " + functionDescriptor;
087    
088            this.state = state;
089            this.typeMapper = state.getTypeMapper();
090            this.codegen = codegen;
091            this.callElement = callElement;
092            this.functionDescriptor = functionDescriptor.getOriginal();
093            bindingContext = codegen.getBindingContext();
094            initialFrameSize = codegen.getFrameMap().getCurrentSize();
095    
096            context = (MethodContext) getContext(functionDescriptor, state);
097            jvmSignature = typeMapper.mapSignature(functionDescriptor, context.getContextKind());
098    
099            InlineStrategy inlineStrategy =
100                    codegen.getContext().isInlineFunction() ? InlineStrategy.IN_PLACE : functionDescriptor.getInlineStrategy();
101            this.asFunctionInline = false;
102    
103            isSameModule = JvmCodegenUtil.isCallInsideSameModuleAsDeclared(functionDescriptor, codegen.getContext(), state.getOutDirectory());
104        }
105    
106        @Override
107        public void genCallWithoutAssertions(
108                @NotNull CallableMethod callableMethod, @NotNull ExpressionCodegen codegen
109        ) {
110            genCall(callableMethod, null, false, codegen);
111        }
112    
113        @Override
114        public void genCall(@NotNull CallableMethod callableMethod, @Nullable ResolvedCall<?> resolvedCall, boolean callDefault, @NotNull ExpressionCodegen codegen) {
115            MethodNode node = null;
116    
117            try {
118                node = createMethodNode(callDefault);
119                endCall(inlineCall(node));
120            }
121            catch (CompilationException e) {
122                throw e;
123            }
124            catch (Exception e) {
125                boolean generateNodeText = !(e instanceof InlineException);
126                PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(this.codegen.getContext().getContextDescriptor());
127                throw new CompilationException("Couldn't inline method call '" +
128                                           functionDescriptor.getName() +
129                                           "' into \n" + (element != null ? element.getText() : "null psi element " + this.codegen.getContext().getContextDescriptor()) +
130                                           (generateNodeText ? ("\ncause: " + InlineCodegenUtil.getNodeText(node)) : ""),
131                                           e, callElement);
132            }
133    
134    
135        }
136    
137        private void endCall(@NotNull InlineResult result) {
138            leaveTemps();
139    
140            state.getFactory().removeInlinedClasses(result.getClassesToRemove());
141        }
142    
143        @NotNull
144        private MethodNode createMethodNode(boolean callDefault) throws ClassNotFoundException, IOException {
145            JvmMethodSignature jvmSignature = typeMapper.mapSignature(functionDescriptor, context.getContextKind());
146    
147            Method asmMethod;
148            if (callDefault) {
149                asmMethod = typeMapper.mapDefaultMethod(functionDescriptor, context.getContextKind(), context);
150            }
151            else {
152                asmMethod = jvmSignature.getAsmMethod();
153            }
154    
155            MethodNode node;
156            if (functionDescriptor instanceof DeserializedSimpleFunctionDescriptor) {
157                VirtualFile file = InlineCodegenUtil.getVirtualFileForCallable((DeserializedSimpleFunctionDescriptor) functionDescriptor, state);
158                node = InlineCodegenUtil.getMethodNode(file.contentsToByteArray(), asmMethod.getName(), asmMethod.getDescriptor());
159    
160                if (node == null) {
161                    throw new RuntimeException("Couldn't obtain compiled function body for " + descriptorName(functionDescriptor));
162                }
163            }
164            else {
165                PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor);
166    
167                if (element == null) {
168                    throw new RuntimeException("Couldn't find declaration for function " + descriptorName(functionDescriptor));
169                }
170    
171                node = new MethodNode(InlineCodegenUtil.API,
172                                               getMethodAsmFlags(functionDescriptor, context.getContextKind()) | (callDefault ? Opcodes.ACC_STATIC : 0),
173                                               asmMethod.getName(),
174                                               asmMethod.getDescriptor(),
175                                               jvmSignature.getGenericsSignature(),
176                                               null);
177    
178                //for maxLocals calculation
179                MethodVisitor maxCalcAdapter = InlineCodegenUtil.wrapWithMaxLocalCalc(node);
180                MethodContext methodContext = context.getParentContext().intoFunction(functionDescriptor);
181                MemberCodegen<?> parentCodegen = codegen.getParentCodegen();
182                if (callDefault) {
183                    boolean isStatic = AsmUtil.isStaticMethod(context.getContextKind(), functionDescriptor);
184                    FunctionCodegen.generateDefaultImplBody(
185                            methodContext, jvmSignature, functionDescriptor, isStatic, maxCalcAdapter, DefaultParameterValueLoader.DEFAULT,
186                            (JetNamedFunction) element, parentCodegen, state
187                    );
188                }
189                else {
190                    FunctionCodegen.generateMethodBody(
191                            maxCalcAdapter, functionDescriptor, methodContext, jvmSignature,
192                            new FunctionGenerationStrategy.FunctionDefault(state, functionDescriptor, (JetDeclarationWithBody) element),
193                            parentCodegen
194                    );
195                }
196                maxCalcAdapter.visitMaxs(-1, -1);
197                maxCalcAdapter.visitEnd();
198            }
199            return node;
200        }
201    
202        private InlineResult inlineCall(MethodNode node) {
203            generateClosuresBodies();
204    
205            //through generation captured parameters will be added to invocationParamBuilder
206            putClosureParametersOnStack();
207    
208            addInlineMarker(codegen.v, true);
209    
210            Parameters parameters = invocationParamBuilder.buildParameters();
211    
212            InliningContext info = new RootInliningContext(expressionMap,
213                                                           state,
214                                                           codegen.getInlineNameGenerator()
215                                                                   .subGenerator(functionDescriptor.getName().asString()),
216                                                           codegen.getContext(),
217                                                           callElement,
218                                                           codegen.getParentCodegen().getClassName());
219    
220            MethodInliner inliner = new MethodInliner(node, parameters, info, new FieldRemapper(null, null, parameters), isSameModule, "Method inlining " + callElement.getText()); //with captured
221    
222            LocalVarRemapper remapper = new LocalVarRemapper(parameters, initialFrameSize);
223    
224    
225            MethodNode adapter = InlineCodegenUtil.createEmptyMethodNode();
226            InlineResult result = inliner.doInline(adapter, remapper, true, LabelOwner.SKIP_ALL);
227    
228            LabelOwner labelOwner = new LabelOwner() {
229    
230                final CallableMemberDescriptor descriptor = codegen.getContext().getContextDescriptor();
231    
232                final boolean isLambda = CodegenBinding.isLocalFunOrLambda(descriptor) && descriptor.getName().isSpecial();
233    
234                @Override
235                public boolean isMyLabel(@NotNull String name) {
236                    if (InlineCodegenUtil.ROOT_LABEL.equals(name)) {
237                        return !isLambda;
238                    }
239                    else {
240                        return descriptor.getName().asString().equals(name);
241                    }
242                }
243            };
244            List<MethodInliner.ExternalFinallyBlockInfo> infos = MethodInliner.processReturns(adapter, labelOwner, true, null);
245            generateAndInsertFinallyBlocks(adapter, infos);
246    
247            adapter.accept(new InliningInstructionAdapter(codegen.v));
248    
249            addInlineMarker(codegen.v, false);
250    
251            return result;
252        }
253    
254        private void generateClosuresBodies() {
255            for (LambdaInfo info : expressionMap.values()) {
256                info.setNode(generateLambdaBody(info));
257            }
258        }
259    
260        private MethodNode generateLambdaBody(LambdaInfo info) {
261            JetFunctionLiteral declaration = info.getFunctionLiteral();
262            FunctionDescriptor descriptor = info.getFunctionDescriptor();
263    
264            MethodContext parentContext = codegen.getContext();
265    
266            MethodContext context = parentContext.intoClosure(descriptor, codegen, typeMapper).intoInlinedLambda(descriptor);
267    
268            JvmMethodSignature jvmMethodSignature = typeMapper.mapSignature(descriptor);
269            Method asmMethod = jvmMethodSignature.getAsmMethod();
270            MethodNode methodNode = new MethodNode(InlineCodegenUtil.API, getMethodAsmFlags(descriptor, context.getContextKind()), asmMethod.getName(), asmMethod.getDescriptor(), jvmMethodSignature.getGenericsSignature(), null);
271    
272            MethodVisitor adapter = InlineCodegenUtil.wrapWithMaxLocalCalc(methodNode);
273    
274            FunctionCodegen.generateMethodBody(adapter, descriptor, context, jvmMethodSignature, new FunctionGenerationStrategy.FunctionDefault(state, descriptor, declaration), codegen.getParentCodegen());
275            adapter.visitMaxs(-1, -1);
276    
277            return methodNode;
278        }
279    
280    
281    
282        @Override
283        public void afterParameterPut(@NotNull Type type, @Nullable StackValue stackValue, @Nullable ValueParameterDescriptor valueParameterDescriptor) {
284            putCapturedInLocal(type, stackValue, valueParameterDescriptor, -1);
285        }
286    
287        private void putCapturedInLocal(
288                @NotNull Type type, @Nullable StackValue stackValue, @Nullable ValueParameterDescriptor valueParameterDescriptor, int capturedParamIndex
289        ) {
290            if (!asFunctionInline && Type.VOID_TYPE != type) {
291                //TODO remap only inlinable closure => otherwise we could get a lot of problem
292                boolean couldBeRemapped = !shouldPutValue(type, stackValue, valueParameterDescriptor);
293                StackValue remappedIndex = couldBeRemapped ? stackValue : null;
294    
295                ParameterInfo info;
296                if (capturedParamIndex >= 0) {
297                    CapturedParamDesc capturedParamInfoInLambda = activeLambda.getCapturedVars().get(capturedParamIndex);
298                    info = invocationParamBuilder.addCapturedParam(capturedParamInfoInLambda, capturedParamInfoInLambda.getFieldName());
299                    info.setRemapValue(remappedIndex);
300                }
301                else {
302                    info = invocationParamBuilder.addNextParameter(type, false, remappedIndex);
303                }
304    
305                putParameterOnStack(info);
306            }
307        }
308    
309        /*descriptor is null for captured vars*/
310        public boolean shouldPutValue(
311                @NotNull Type type,
312                @Nullable StackValue stackValue,
313                @Nullable ValueParameterDescriptor descriptor
314        ) {
315    
316            if (stackValue == null) {
317                //default or vararg
318                return true;
319            }
320    
321            //remap only inline functions (and maybe non primitives)
322            //TODO - clean asserion and remapping logic
323            if (isPrimitive(type) != isPrimitive(stackValue.type)) {
324                //don't remap boxing/unboxing primitives - lost identity and perfomance
325                return true;
326            }
327    
328            if (stackValue instanceof StackValue.Local) {
329                return false;
330            }
331    
332            if (stackValue instanceof StackValue.Composed) {
333                //see: Method.isSpecialStackValue: go through aload 0
334                if (codegen.getContext().isInliningLambda() && codegen.getContext().getContextDescriptor() instanceof AnonymousFunctionDescriptor) {
335                    if (descriptor != null && !InlineUtil.hasNoinlineAnnotation(descriptor)) {
336                        //TODO: check type of context
337                        return false;
338                    }
339                }
340            }
341            return true;
342        }
343    
344        private void putParameterOnStack(ParameterInfo... infos) {
345            int[] index = new int[infos.length];
346            for (int i = 0; i < infos.length; i++) {
347                ParameterInfo info = infos[i];
348                if (!info.isSkippedOrRemapped()) {
349                    index[i] = codegen.getFrameMap().enterTemp(info.getType());
350                }
351                else {
352                    index[i] = -1;
353                }
354            }
355    
356            for (int i = infos.length - 1; i >= 0; i--) {
357                ParameterInfo info = infos[i];
358                if (!info.isSkippedOrRemapped()) {
359                    Type type = info.type;
360                    StackValue.local(index[i], type).store(type, codegen.v);
361                }
362            }
363        }
364    
365        @Override
366        public void putHiddenParams() {
367            List<JvmMethodParameterSignature> valueParameters = jvmSignature.getValueParameters();
368    
369            if (!isStaticMethod(functionDescriptor, context)) {
370                invocationParamBuilder.addNextParameter(AsmTypeConstants.OBJECT_TYPE, false, null);
371            }
372    
373            for (JvmMethodParameterSignature param : valueParameters) {
374                if (param.getKind() == JvmMethodParameterKind.VALUE) {
375                    break;
376                }
377                invocationParamBuilder.addNextParameter(param.getAsmType(), false, null);
378            }
379    
380            List<ParameterInfo> infos = invocationParamBuilder.listNotCaptured();
381            putParameterOnStack(infos.toArray(new ParameterInfo[infos.size()]));
382        }
383    
384        public void leaveTemps() {
385            FrameMap frameMap = codegen.getFrameMap();
386            List<ParameterInfo> infos = invocationParamBuilder.listAllParams();
387            for (ListIterator<? extends ParameterInfo> iterator = infos.listIterator(infos.size()); iterator.hasPrevious(); ) {
388                ParameterInfo param = iterator.previous();
389                if (!param.isSkippedOrRemapped()) {
390                    frameMap.leaveTemp(param.type);
391                }
392            }
393        }
394    
395        public static boolean isInliningClosure(JetExpression expression, ValueParameterDescriptor valueParameterDescriptora) {
396            //TODO deparenthisise typed
397            JetExpression deparenthesize = JetPsiUtil.deparenthesize(expression);
398            return deparenthesize instanceof JetFunctionLiteralExpression &&
399                   !InlineUtil.hasNoinlineAnnotation(valueParameterDescriptora);
400        }
401    
402        public void rememberClosure(JetExpression expression, Type type) {
403            JetFunctionLiteralExpression lambda = (JetFunctionLiteralExpression) JetPsiUtil.deparenthesize(expression);
404            assert lambda != null : "Couldn't find lambda in " + expression.getText();
405    
406            String labelNameIfPresent = null;
407            PsiElement parent = lambda.getParent();
408            if (parent instanceof JetLabeledExpression) {
409                labelNameIfPresent = ((JetLabeledExpression) parent).getLabelName();
410            }
411            LambdaInfo info = new LambdaInfo(lambda, typeMapper, labelNameIfPresent);
412    
413            ParameterInfo closureInfo = invocationParamBuilder.addNextParameter(type, true, null);
414            closureInfo.setLambda(info);
415            expressionMap.put(closureInfo.getIndex(), info);
416        }
417    
418        private void putClosureParametersOnStack() {
419            for (LambdaInfo next : expressionMap.values()) {
420                activeLambda = next;
421                codegen.pushClosureOnStack(next.closure, false, this);
422            }
423            activeLambda = null;
424        }
425    
426        public static CodegenContext getContext(DeclarationDescriptor descriptor, GenerationState state) {
427            if (descriptor instanceof PackageFragmentDescriptor) {
428                return new PackageContext((PackageFragmentDescriptor) descriptor, null, null);
429            }
430    
431            CodegenContext parent = getContext(descriptor.getContainingDeclaration(), state);
432    
433            if (descriptor instanceof ClassDescriptor) {
434                OwnerKind kind = DescriptorUtils.isTrait(descriptor) ? OwnerKind.TRAIT_IMPL : OwnerKind.IMPLEMENTATION;
435                return parent.intoClass((ClassDescriptor) descriptor, kind, state);
436            }
437            else if (descriptor instanceof FunctionDescriptor) {
438                return parent.intoFunction((FunctionDescriptor) descriptor);
439            }
440    
441            throw new IllegalStateException("Couldn't build context for " + descriptorName(descriptor));
442        }
443    
444        private static boolean isStaticMethod(FunctionDescriptor functionDescriptor, MethodContext context) {
445            return (getMethodAsmFlags(functionDescriptor, context.getContextKind()) & Opcodes.ACC_STATIC) != 0;
446        }
447    
448        private static String descriptorName(DeclarationDescriptor descriptor) {
449            return DescriptorRenderer.SHORT_NAMES_IN_TYPES.render(descriptor);
450        }
451    
452        @Override
453        public void genValueAndPut(
454                @NotNull ValueParameterDescriptor valueParameterDescriptor,
455                @NotNull JetExpression argumentExpression,
456                @NotNull Type parameterType
457        ) {
458            //TODO deparenthisise
459            if (isInliningClosure(argumentExpression, valueParameterDescriptor)) {
460                rememberClosure(argumentExpression, parameterType);
461            } else {
462                StackValue value = codegen.gen(argumentExpression);
463                putValueIfNeeded(valueParameterDescriptor, parameterType, value);
464            }
465        }
466    
467        @Override
468        public void putValueIfNeeded(@Nullable ValueParameterDescriptor valueParameterDescriptor, @NotNull Type parameterType, @NotNull StackValue value) {
469            if (shouldPutValue(parameterType, value, valueParameterDescriptor)) {
470                value.put(parameterType, codegen.v);
471            }
472            afterParameterPut(parameterType, value, valueParameterDescriptor);
473        }
474    
475        @Override
476        public void putCapturedValueOnStack(
477                @NotNull StackValue stackValue, @NotNull Type valueType, int paramIndex
478        ) {
479            if (shouldPutValue(stackValue.type, stackValue, null)) {
480                stackValue.put(stackValue.type, codegen.v);
481            }
482            putCapturedInLocal(stackValue.type, stackValue, null, paramIndex);
483        }
484    
485    
486        public void generateAndInsertFinallyBlocks(MethodNode intoNode, List<MethodInliner.ExternalFinallyBlockInfo> insertPoints) {
487            if (!codegen.hasFinallyBlocks()) return;
488    
489            for (MethodInliner.ExternalFinallyBlockInfo insertPoint : insertPoints) {
490                MethodNode finallyNode = InlineCodegenUtil.createEmptyMethodNode();
491                ExpressionCodegen finallyCodegen =
492                        new ExpressionCodegen(finallyNode, codegen.getFrameMap(), codegen.getReturnType(),
493                                              codegen.getContext(), codegen.getState(), codegen.getParentCodegen());
494                finallyCodegen.addBlockStackElementsForNonLocalReturns(codegen.getBlockStackElements());
495    
496                finallyCodegen.generateFinallyBlocksIfNeeded(insertPoint.returnType);
497    
498                InlineCodegenUtil.insertNodeBefore(finallyNode, intoNode, insertPoint.beforeIns);
499            }
500        }
501    
502    }