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
017package org.jetbrains.jet.lang.cfg.pseudocode;
018
019import com.google.common.collect.*;
020import org.jetbrains.annotations.NotNull;
021import org.jetbrains.annotations.Nullable;
022import org.jetbrains.jet.lang.cfg.Label;
023import org.jetbrains.jet.lang.cfg.LoopInfo;
024import org.jetbrains.jet.lang.psi.JetElement;
025import org.jetbrains.jet.lang.psi.JetExpression;
026
027import java.util.*;
028
029public class PseudocodeImpl implements Pseudocode {
030
031    public class PseudocodeLabel implements Label {
032        private final String name;
033        private Integer targetInstructionIndex;
034
035
036        private PseudocodeLabel(String name) {
037            this.name = name;
038        }
039
040        @Override
041        public String getName() {
042            return name;
043        }
044
045        @Override
046        public String toString() {
047            return name;
048        }
049
050        public Integer getTargetInstructionIndex() {
051            return targetInstructionIndex;
052        }
053
054        public void setTargetInstructionIndex(int targetInstructionIndex) {
055            this.targetInstructionIndex = targetInstructionIndex;
056        }
057
058        @Nullable
059        private List<Instruction> resolve() {
060            assert targetInstructionIndex != null;
061            return mutableInstructionList.subList(getTargetInstructionIndex(), mutableInstructionList.size());
062        }
063
064        public Instruction resolveToInstruction() {
065            assert targetInstructionIndex != null;
066            return mutableInstructionList.get(targetInstructionIndex);
067        }
068
069        public Label copy() {
070            return new PseudocodeLabel("copy " + name);
071        }
072    }
073
074    private final List<Instruction> mutableInstructionList = new ArrayList<Instruction>();
075    private final List<Instruction> instructions = new ArrayList<Instruction>();
076
077    private Set<LocalDeclarationInstruction> localDeclarations = null;
078    //todo getters
079    private final Map<JetElement, Instruction> representativeInstructions = new HashMap<JetElement, Instruction>();
080    private final Map<JetExpression, LoopInfo> loopInfo = Maps.newHashMap();
081    
082    private final List<PseudocodeLabel> labels = new ArrayList<PseudocodeLabel>();
083
084    private final JetElement correspondingElement;
085    private SubroutineExitInstruction exitInstruction;
086    private SubroutineSinkInstruction sinkInstruction;
087    private SubroutineExitInstruction errorInstruction;
088    private boolean postPrecessed = false;
089
090    public PseudocodeImpl(JetElement correspondingElement) {
091        this.correspondingElement = correspondingElement;
092    }
093
094    @NotNull
095    @Override
096    public JetElement getCorrespondingElement() {
097        return correspondingElement;
098    }
099
100    @NotNull
101    @Override
102    public Set<LocalDeclarationInstruction> getLocalDeclarations() {
103        if (localDeclarations == null) {
104            localDeclarations = getLocalDeclarations(this);
105        }
106        return localDeclarations;
107    }
108
109    @NotNull
110    private static Set<LocalDeclarationInstruction> getLocalDeclarations(@NotNull Pseudocode pseudocode) {
111        Set<LocalDeclarationInstruction> localDeclarations = Sets.newLinkedHashSet();
112        for (Instruction instruction : pseudocode.getInstructions()) {
113            if (instruction instanceof LocalDeclarationInstruction) {
114                localDeclarations.add((LocalDeclarationInstruction) instruction);
115                localDeclarations.addAll(getLocalDeclarations(((LocalDeclarationInstruction)instruction).getBody()));
116            }
117        }
118        return localDeclarations;
119    }
120
121    /*package*/ PseudocodeLabel createLabel(String name) {
122        PseudocodeLabel label = new PseudocodeLabel(name);
123        labels.add(label);
124        return label;
125    }
126    
127    @Override
128    @NotNull
129    public List<Instruction> getInstructions() {
130        return instructions;
131    }
132
133    @NotNull
134    @Override
135    public List<Instruction> getReversedInstructions() {
136        LinkedHashSet<Instruction> traversedInstructions = Sets.newLinkedHashSet();
137        traverseFollowingInstructions(sinkInstruction, traversedInstructions, false);
138        if (traversedInstructions.size() < instructions.size()) {
139            List<Instruction> simplyReversedInstructions = Lists.newArrayList(instructions);
140            Collections.reverse(simplyReversedInstructions);
141            for (Instruction instruction : simplyReversedInstructions) {
142                if (!traversedInstructions.contains(instruction)) {
143                    traverseFollowingInstructions(instruction, traversedInstructions, false);
144                }
145            }
146        }
147        return Lists.newArrayList(traversedInstructions);
148    }
149
150    //for tests only
151    @NotNull
152    public List<Instruction> getAllInstructions() {
153        return mutableInstructionList;
154    }
155
156    @Override
157    @NotNull
158    public List<Instruction> getDeadInstructions() {
159        List<Instruction> deadInstructions = Lists.newArrayList();
160        for (Instruction instruction : mutableInstructionList) {
161            if (isDead(instruction)) {
162                deadInstructions.add(instruction);
163            }
164        }
165        return deadInstructions;
166    }
167
168    private static boolean isDead(@NotNull Instruction instruction) {
169        if (!((InstructionImpl)instruction).isDead()) return false;
170        for (Instruction copy : instruction.getCopies()) {
171            if (!((InstructionImpl)copy).isDead()) return false;
172        }
173        return true;
174    }
175
176    //for tests only
177    @NotNull
178    public List<PseudocodeLabel> getLabels() {
179        return labels;
180    }
181
182    /*package*/ void addExitInstruction(SubroutineExitInstruction exitInstruction) {
183        addInstruction(exitInstruction);
184        assert this.exitInstruction == null;
185        this.exitInstruction = exitInstruction;
186    }
187    
188    /*package*/ void addSinkInstruction(SubroutineSinkInstruction sinkInstruction) {
189        addInstruction(sinkInstruction);
190        assert this.sinkInstruction == null;
191        this.sinkInstruction = sinkInstruction;
192    }
193
194    /*package*/ void addErrorInstruction(SubroutineExitInstruction errorInstruction) {
195        addInstruction(errorInstruction);
196        assert this.errorInstruction == null;
197        this.errorInstruction = errorInstruction;
198    }
199
200    /*package*/ void addInstruction(Instruction instruction) {
201        mutableInstructionList.add(instruction);
202        instruction.setOwner(this);
203
204        if (instruction instanceof JetElementInstruction) {
205            JetElementInstruction elementInstruction = (JetElementInstruction) instruction;
206            representativeInstructions.put(elementInstruction.getElement(), instruction);
207        }
208    }
209
210    /*package*/ void recordLoopInfo(JetExpression expression, LoopInfo blockInfo) {
211        loopInfo.put(expression, blockInfo);
212    }
213
214    @Override
215    @NotNull
216    public SubroutineExitInstruction getExitInstruction() {
217        return exitInstruction;
218    }
219
220    @Override
221    @NotNull
222    public SubroutineSinkInstruction getSinkInstruction() {
223        return sinkInstruction;
224    }
225
226    @Override
227    @NotNull
228    public SubroutineEnterInstruction getEnterInstruction() {
229        return (SubroutineEnterInstruction) mutableInstructionList.get(0);
230    }
231
232    /*package*/ void bindLabel(Label label) {
233        ((PseudocodeLabel) label).setTargetInstructionIndex(mutableInstructionList.size());
234    }
235
236    public void postProcess() {
237        if (postPrecessed) return;
238        postPrecessed = true;
239        errorInstruction.setSink(getSinkInstruction());
240        exitInstruction.setSink(getSinkInstruction());
241        for (int i = 0, instructionsSize = mutableInstructionList.size(); i < instructionsSize; i++) {
242            processInstruction(mutableInstructionList.get(i), i);
243        }
244        Set<Instruction> reachableInstructions = collectReachableInstructions();
245        for (Instruction instruction : mutableInstructionList) {
246            if (reachableInstructions.contains(instruction)) {
247                instructions.add(instruction);
248            }
249        }
250        markDeadInstructions();
251    }
252
253    private void processInstruction(Instruction instruction, final int currentPosition) {
254        instruction.accept(new InstructionVisitor() {
255            @Override
256            public void visitInstructionWithNext(InstructionWithNext instruction) {
257                instruction.setNext(getNextPosition(currentPosition));
258            }
259
260            @Override
261            public void visitJump(AbstractJumpInstruction instruction) {
262                instruction.setResolvedTarget(getJumpTarget(instruction.getTargetLabel()));
263            }
264
265            @Override
266            public void visitNondeterministicJump(NondeterministicJumpInstruction instruction) {
267                instruction.setNext(getNextPosition(currentPosition));
268                List<Label> targetLabels = instruction.getTargetLabels();
269                for (Label targetLabel : targetLabels) {
270                    instruction.setResolvedTarget(targetLabel, getJumpTarget(targetLabel));
271                }
272            }
273
274            @Override
275            public void visitConditionalJump(ConditionalJumpInstruction instruction) {
276                Instruction nextInstruction = getNextPosition(currentPosition);
277                Instruction jumpTarget = getJumpTarget(instruction.getTargetLabel());
278                if (instruction.onTrue()) {
279                    instruction.setNextOnFalse(nextInstruction);
280                    instruction.setNextOnTrue(jumpTarget);
281                }
282                else {
283                    instruction.setNextOnFalse(jumpTarget);
284                    instruction.setNextOnTrue(nextInstruction);
285                }
286                visitJump(instruction);
287            }
288
289            @Override
290            public void visitLocalDeclarationInstruction(LocalDeclarationInstruction instruction) {
291                ((PseudocodeImpl)instruction.getBody()).postProcess();
292                instruction.setNext(getSinkInstruction());
293            }
294
295            @Override
296            public void visitSubroutineExit(SubroutineExitInstruction instruction) {
297                // Nothing
298            }
299
300            @Override
301            public void visitSubroutineSink(SubroutineSinkInstruction instruction) {
302                // Nothing
303            }
304
305            @Override
306            public void visitInstruction(Instruction instruction) {
307                throw new UnsupportedOperationException(instruction.toString());
308            }
309        });
310    }
311
312    private Set<Instruction> collectReachableInstructions() {
313        Set<Instruction> visited = Sets.newHashSet();
314        traverseFollowingInstructions(getEnterInstruction(), visited, true);
315        if (!visited.contains(getExitInstruction())) {
316            visited.add(getExitInstruction());
317        }
318        if (!visited.contains(errorInstruction)) {
319            visited.add(errorInstruction);
320        }
321        if (!visited.contains(getSinkInstruction())) {
322            visited.add(getSinkInstruction());
323        }
324        return visited;
325    }
326    
327    private static void traverseFollowingInstructions(
328            @NotNull Instruction rootInstruction,
329            @NotNull Set<Instruction> visited,
330            boolean directOrder
331    ) {
332        Deque<Instruction> stack = Queues.newArrayDeque();
333        stack.push(rootInstruction);
334
335        while (!stack.isEmpty()) {
336            Instruction instruction = stack.pop();
337            visited.add(instruction);
338
339            Collection<Instruction> followingInstructions =
340                    directOrder ? instruction.getNextInstructions() : instruction.getPreviousInstructions();
341
342            for (Instruction followingInstruction : followingInstructions) {
343                if (followingInstruction != null && !visited.contains(followingInstruction)) {
344                    stack.push(followingInstruction);
345                }
346            }
347        }
348    }
349
350    private void markDeadInstructions() {
351        Set<Instruction> instructionSet = Sets.newHashSet(instructions);
352        for (Instruction instruction : mutableInstructionList) {
353            if (!instructionSet.contains(instruction)) {
354                ((InstructionImpl)instruction).die();
355                for (Instruction nextInstruction : instruction.getNextInstructions()) {
356                    nextInstruction.getPreviousInstructions().remove(instruction);
357                }
358            }
359        }
360    }
361
362    @NotNull
363    private Instruction getJumpTarget(@NotNull Label targetLabel) {
364        return ((PseudocodeLabel)targetLabel).resolveToInstruction();
365    }
366
367    @NotNull
368    private Instruction getNextPosition(int currentPosition) {
369        int targetPosition = currentPosition + 1;
370        assert targetPosition < mutableInstructionList.size() : currentPosition;
371        return mutableInstructionList.get(targetPosition);
372    }
373
374    public void repeatPart(@NotNull Label startLabel, @NotNull Label finishLabel) {
375        Integer startIndex = ((PseudocodeLabel) startLabel).getTargetInstructionIndex();
376        assert startIndex != null;
377        Integer finishIndex = ((PseudocodeLabel) finishLabel).getTargetInstructionIndex();
378        assert finishIndex != null;
379
380        Map<Label, Label> originalToCopy = Maps.newHashMap();
381        Multimap<Instruction, Label> originalLabelsForInstruction = HashMultimap.create();
382        for (PseudocodeLabel label : labels) {
383            Integer index = label.getTargetInstructionIndex();
384            if (index == null) continue; //label is not bounded yet
385            if (label == startLabel || label == finishLabel) continue;
386
387            if (startIndex <= index && index <= finishIndex) {
388                originalToCopy.put(label, label.copy());
389                originalLabelsForInstruction.put(getJumpTarget(label), label);
390            }
391        }
392        for (int index = startIndex; index < finishIndex; index++) {
393            Instruction originalInstruction = mutableInstructionList.get(index);
394            repeatLabelsBindingForInstruction(originalInstruction, originalToCopy, originalLabelsForInstruction);
395            addInstruction(copyInstruction(originalInstruction, originalToCopy));
396        }
397        repeatLabelsBindingForInstruction(mutableInstructionList.get(finishIndex), originalToCopy, originalLabelsForInstruction);
398    }
399
400    private void repeatLabelsBindingForInstruction(
401            @NotNull Instruction originalInstruction,
402            @NotNull Map<Label, Label> originalToCopy,
403            @NotNull Multimap<Instruction, Label> originalLabelsForInstruction
404    ) {
405        for (Label originalLabel : originalLabelsForInstruction.get(originalInstruction)) {
406            bindLabel(originalToCopy.get(originalLabel));
407        }
408    }
409
410    private Instruction copyInstruction(@NotNull Instruction instruction, @NotNull Map<Label, Label> originalToCopy) {
411        if (instruction instanceof AbstractJumpInstruction) {
412            Label originalTarget = ((AbstractJumpInstruction) instruction).getTargetLabel();
413            if (originalToCopy.containsKey(originalTarget)) {
414                return ((AbstractJumpInstruction)instruction).copy(originalToCopy.get(originalTarget));
415            }
416        }
417        if (instruction instanceof NondeterministicJumpInstruction) {
418            List<Label> originalTargets = ((NondeterministicJumpInstruction) instruction).getTargetLabels();
419            List<Label> copyTargets = copyLabels(originalTargets, originalToCopy);
420            return ((NondeterministicJumpInstruction) instruction).copy(copyTargets);
421        }
422        return ((InstructionImpl)instruction).copy();
423    }
424
425    @NotNull
426    private List<Label> copyLabels(Collection<Label> labels, Map<Label, Label> originalToCopy) {
427        List<Label> newLabels = Lists.newArrayList();
428        for (Label label : labels) {
429            Label newLabel = originalToCopy.get(label);
430            newLabels.add(newLabel != null ? newLabel : label);
431        }
432        return newLabels;
433    }
434}