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.lang.cfg;
018    
019    import com.google.common.collect.Lists;
020    import com.google.common.collect.Maps;
021    import com.google.common.collect.Queues;
022    import com.google.common.collect.Sets;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.annotations.Nullable;
025    import org.jetbrains.jet.lang.cfg.pseudocode.*;
026    
027    import java.util.*;
028    
029    import static org.jetbrains.jet.lang.cfg.PseudocodeTraverser.TraversalOrder.FORWARD;
030    
031    public class PseudocodeTraverser {
032        
033        public static enum TraversalOrder {
034            FORWARD,
035            BACKWARD
036        }
037        
038        @NotNull
039        private static Instruction getStartInstruction(@NotNull Pseudocode pseudocode, @NotNull TraversalOrder traversalOrder) {
040            return traversalOrder == FORWARD ? pseudocode.getEnterInstruction() : pseudocode.getSinkInstruction();
041        }
042    
043        @NotNull
044        private static Instruction getLastInstruction(@NotNull Pseudocode pseudocode, @NotNull TraversalOrder traversalOrder) {
045            return traversalOrder == FORWARD ? pseudocode.getSinkInstruction() : pseudocode.getEnterInstruction();
046        }
047    
048        @NotNull
049        private static List<Instruction> getInstructions(@NotNull Pseudocode pseudocode, @NotNull TraversalOrder traversalOrder) {
050            return traversalOrder == FORWARD ? pseudocode.getInstructions() : pseudocode.getReversedInstructions();
051        }
052    
053        @NotNull
054        private static Collection<Instruction> getPreviousInstruction(@NotNull Instruction instruction, @NotNull TraversalOrder traversalOrder) {
055            return traversalOrder == FORWARD ? instruction.getPreviousInstructions() : instruction.getNextInstructions();
056        }
057    
058        private static boolean isStartInstruction(@NotNull Instruction instruction, @NotNull TraversalOrder traversalOrder) {
059            return traversalOrder == FORWARD ? instruction instanceof SubroutineEnterInstruction
060                                             : instruction instanceof SubroutineSinkInstruction;
061        }
062    
063        public static enum LookInsideStrategy {
064            ANALYSE_LOCAL_DECLARATIONS,
065            SKIP_LOCAL_DECLARATIONS
066        }
067    
068        private static boolean shouldLookInside(Instruction instruction, LookInsideStrategy lookInside) {
069            return lookInside == LookInsideStrategy.ANALYSE_LOCAL_DECLARATIONS && instruction instanceof LocalFunctionDeclarationInstruction;
070        }
071    
072        public static <D> Map<Instruction, Edges<D>> collectData(
073                @NotNull Pseudocode pseudocode, TraversalOrder traversalOrder, LookInsideStrategy lookInside,
074                @NotNull D initialDataValue, @NotNull D initialDataValueForEnterInstruction,
075                @NotNull InstructionDataMergeStrategy<D> instructionDataMergeStrategy) {
076    
077            Map<Instruction, Edges<D>> edgesMap = Maps.newLinkedHashMap();
078            initializeEdgesMap(pseudocode, lookInside, edgesMap, initialDataValue);
079            edgesMap.put(getStartInstruction(pseudocode, traversalOrder), Edges.create(initialDataValueForEnterInstruction, initialDataValueForEnterInstruction));
080    
081            boolean[] changed = new boolean[1];
082            changed[0] = true;
083            while (changed[0]) {
084                changed[0] = false;
085                collectDataFromSubgraph(pseudocode, traversalOrder, lookInside, edgesMap, instructionDataMergeStrategy,
086                                        Collections.<Instruction>emptyList(), changed, false);
087            }
088            return edgesMap;
089        }
090    
091        private static <D> void initializeEdgesMap(
092                @NotNull Pseudocode pseudocode, LookInsideStrategy lookInside,
093                @NotNull Map<Instruction, Edges<D>> edgesMap,
094                @NotNull D initialDataValue) {
095            List<Instruction> instructions = pseudocode.getInstructions();
096            Edges<D> initialEdge = Edges.create(initialDataValue, initialDataValue);
097            for (Instruction instruction : instructions) {
098                edgesMap.put(instruction, initialEdge);
099                if (shouldLookInside(instruction, lookInside)) {
100                    initializeEdgesMap(((LocalFunctionDeclarationInstruction) instruction).getBody(), lookInside, edgesMap, initialDataValue);
101                }
102            }
103        }
104    
105        private static <D> void collectDataFromSubgraph(
106                @NotNull Pseudocode pseudocode, TraversalOrder traversalOrder,
107                LookInsideStrategy lookInside,
108                @NotNull Map<Instruction, Edges<D>> edgesMap,
109                @NotNull InstructionDataMergeStrategy<D> instructionDataMergeStrategy,
110                @NotNull Collection<Instruction> previousSubGraphInstructions,
111                boolean[] changed, boolean isLocal) {
112    
113            List<Instruction> instructions = getInstructions(pseudocode, traversalOrder);
114            Instruction startInstruction = getStartInstruction(pseudocode, traversalOrder);
115    
116            for (Instruction instruction : instructions) {
117                boolean isStart = isStartInstruction(instruction, traversalOrder);
118                if (!isLocal && isStart) continue;
119    
120                Collection<Instruction> allPreviousInstructions;
121                Collection<Instruction> previousInstructions = getPreviousInstruction(instruction, traversalOrder);
122    
123                if (instruction == startInstruction && !previousSubGraphInstructions.isEmpty()) {
124                    allPreviousInstructions = Lists.newArrayList(previousInstructions);
125                    allPreviousInstructions.addAll(previousSubGraphInstructions);
126                }
127                else {
128                    allPreviousInstructions = previousInstructions;
129                }
130    
131                if (shouldLookInside(instruction, lookInside)) {
132                    Pseudocode subroutinePseudocode = ((LocalFunctionDeclarationInstruction) instruction).getBody();
133                    collectDataFromSubgraph(subroutinePseudocode, traversalOrder, lookInside, edgesMap, instructionDataMergeStrategy,
134                                            previousInstructions,
135                                            changed, true);
136                    Instruction lastInstruction = getLastInstruction(subroutinePseudocode, traversalOrder);
137                    Edges<D> previousValue = edgesMap.get(instruction);
138                    Edges<D> newValue = edgesMap.get(lastInstruction);
139                    if (!previousValue.equals(newValue)) {
140                        changed[0] = true;
141                        edgesMap.put(instruction, newValue);
142                    }
143                    continue;
144                }
145                Edges<D> previousDataValue = edgesMap.get(instruction);
146    
147                Collection<D> incomingEdgesData = Sets.newHashSet();
148    
149                for (Instruction previousInstruction : allPreviousInstructions) {
150                    Edges<D> previousData = edgesMap.get(previousInstruction);
151                    if (previousData != null) {
152                        incomingEdgesData.add(previousData.out);
153                    }
154                }
155                Edges<D> mergedData = instructionDataMergeStrategy.execute(instruction, incomingEdgesData);
156                if (!mergedData.equals(previousDataValue)) {
157                    changed[0] = true;
158                    edgesMap.put(instruction, mergedData);
159                }
160            }
161        }
162    
163        public static void traverse(
164                @NotNull Pseudocode pseudocode, TraversalOrder traversalOrder,
165                InstructionAnalyzeStrategy instructionAnalyzeStrategy) {
166    
167            List<Instruction> instructions = getInstructions(pseudocode, traversalOrder);
168            for (Instruction instruction : instructions) {
169                if (instruction instanceof LocalFunctionDeclarationInstruction) {
170                    traverse(((LocalFunctionDeclarationInstruction) instruction).getBody(), traversalOrder, instructionAnalyzeStrategy);
171                }
172                instructionAnalyzeStrategy.execute(instruction);
173            }
174        }
175    
176        public static <D> void traverse(
177                @NotNull Pseudocode pseudocode, TraversalOrder traversalOrder,
178                @NotNull Map<Instruction, Edges<D>> edgesMap,
179                @NotNull InstructionDataAnalyzeStrategy<D> instructionDataAnalyzeStrategy) {
180    
181            List<Instruction> instructions = getInstructions(pseudocode, traversalOrder);
182            for (Instruction instruction : instructions) {
183                if (instruction instanceof LocalFunctionDeclarationInstruction) {
184                    traverse(((LocalFunctionDeclarationInstruction) instruction).getBody(), traversalOrder, edgesMap,
185                             instructionDataAnalyzeStrategy);
186                }
187                Edges<D> edges = edgesMap.get(instruction);
188                instructionDataAnalyzeStrategy.execute(instruction, edges != null ? edges.in : null, edges != null ? edges.out : null);
189            }
190        }
191    
192        public interface InstructionDataMergeStrategy<D> {
193            Edges<D> execute(@NotNull Instruction instruction, @NotNull Collection<D> incomingEdgesData);
194        }
195    
196        public interface InstructionDataAnalyzeStrategy<D> {
197            void execute(@NotNull Instruction instruction, @Nullable D enterData, @Nullable D exitData);
198        }
199    
200        public interface InstructionAnalyzeStrategy {
201            void execute(@NotNull Instruction instruction);
202        }
203    
204        public static class Edges<T> {
205            public final T in;
206            public final T out;
207    
208            Edges(@NotNull T in, @NotNull T out) {
209                this.in = in;
210                this.out = out;
211            }
212    
213            public static <T> Edges<T> create(@NotNull T in, @NotNull T out) {
214                return new Edges<T>(in, out);
215            }
216    
217            @Override
218            public boolean equals(Object o) {
219                if (this == o) return true;
220                if (!(o instanceof Edges)) return false;
221    
222                Edges edges = (Edges) o;
223    
224                if (in != null ? !in.equals(edges.in) : edges.in != null) return false;
225                if (out != null ? !out.equals(edges.out) : edges.out != null) return false;
226    
227                return true;
228            }
229    
230            @Override
231            public int hashCode() {
232                int result = in != null ? in.hashCode() : 0;
233                result = 31 * result + (out != null ? out.hashCode() : 0);
234                return result;
235            }
236        }
237    
238        public interface InstructionHandler {
239            // true to continue traversal
240            boolean handle(@NotNull Instruction instruction);
241        }
242    
243        // returns false when interrupted by handler
244        public static boolean traverseFollowingInstructions(
245                @NotNull Instruction rootInstruction,
246                @NotNull Set<Instruction> visited,
247                @NotNull TraversalOrder order,
248                @Nullable InstructionHandler handler
249        ) {
250            Deque<Instruction> stack = Queues.newArrayDeque();
251            stack.push(rootInstruction);
252    
253            while (!stack.isEmpty()) {
254                Instruction instruction = stack.pop();
255                visited.add(instruction);
256    
257                Collection<Instruction> followingInstructions =
258                        order == FORWARD ? instruction.getNextInstructions() : instruction.getPreviousInstructions();
259    
260                for (Instruction followingInstruction : followingInstructions) {
261                    if (followingInstruction != null && !visited.contains(followingInstruction)) {
262                        if (handler != null && !handler.handle(instruction)) return false;
263                        stack.push(followingInstruction);
264                    }
265                }
266            }
267            return true;
268        }
269    
270    }