001/*
002 * Copyright (c) 2014, 2014, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.lir.constopt;
024
025import static com.oracle.graal.lir.LIRValueUtil.*;
026import static com.oracle.graal.lir.phases.LIRPhase.Options.*;
027import static jdk.internal.jvmci.code.ValueUtil.*;
028
029import java.util.*;
030
031import jdk.internal.jvmci.code.*;
032import com.oracle.graal.debug.*;
033import com.oracle.graal.debug.Debug.*;
034import jdk.internal.jvmci.meta.*;
035import jdk.internal.jvmci.options.*;
036
037import com.oracle.graal.compiler.common.cfg.*;
038import com.oracle.graal.lir.*;
039import com.oracle.graal.lir.LIRInstruction.OperandFlag;
040import com.oracle.graal.lir.LIRInstruction.OperandMode;
041import com.oracle.graal.lir.StandardOp.MoveOp;
042import com.oracle.graal.lir.constopt.ConstantTree.Flags;
043import com.oracle.graal.lir.constopt.ConstantTree.NodeCost;
044import com.oracle.graal.lir.gen.*;
045import com.oracle.graal.lir.phases.*;
046
047/**
048 * This optimization tries to improve the handling of constants by replacing a single definition of
049 * a constant, which is potentially scheduled into a block with high probability, with one or more
050 * definitions in blocks with a lower probability.
051 */
052public final class ConstantLoadOptimization extends PreAllocationOptimizationPhase {
053
054    public static class Options {
055        // @formatter:off
056        @Option(help = "Enable constant load optimization.", type = OptionType.Debug)
057        public static final NestedBooleanOptionValue LIROptConstantLoadOptimization = new NestedBooleanOptionValue(LIROptimization, true);
058        // @formatter:on
059    }
060
061    @Override
062    protected <B extends AbstractBlockBase<B>> void run(TargetDescription target, LIRGenerationResult lirGenRes, List<B> codeEmittingOrder, List<B> linearScanOrder, LIRGeneratorTool lirGen) {
063        new Optimization(lirGenRes.getLIR(), lirGen).apply();
064    }
065
066    private static final DebugMetric constantsTotal = Debug.metric("ConstantLoadOptimization[total]");
067    private static final DebugMetric phiConstantsSkipped = Debug.metric("ConstantLoadOptimization[PhisSkipped]");
068    private static final DebugMetric singleUsageConstantsSkipped = Debug.metric("ConstantLoadOptimization[SingleUsageSkipped]");
069    private static final DebugMetric usageAtDefinitionSkipped = Debug.metric("ConstantLoadOptimization[UsageAtDefinitionSkipped]");
070    private static final DebugMetric materializeAtDefinitionSkipped = Debug.metric("ConstantLoadOptimization[MaterializeAtDefinitionSkipped]");
071    private static final DebugMetric constantsOptimized = Debug.metric("ConstantLoadOptimization[optimized]");
072
073    private static final class Optimization {
074        private final LIR lir;
075        private final LIRGeneratorTool lirGen;
076        private final VariableMap<DefUseTree> map;
077        private final BitSet phiConstants;
078        private final BitSet defined;
079        private final BlockMap<List<UseEntry>> blockMap;
080        private final BlockMap<LIRInsertionBuffer> insertionBuffers;
081
082        private Optimization(LIR lir, LIRGeneratorTool lirGen) {
083            this.lir = lir;
084            this.lirGen = lirGen;
085            this.map = new VariableMap<>();
086            this.phiConstants = new BitSet();
087            this.defined = new BitSet();
088            this.insertionBuffers = new BlockMap<>(lir.getControlFlowGraph());
089            this.blockMap = new BlockMap<>(lir.getControlFlowGraph());
090        }
091
092        private void apply() {
093            try (Indent indent = Debug.logAndIndent("ConstantLoadOptimization")) {
094                try (Scope s = Debug.scope("BuildDefUseTree")) {
095                    // build DefUseTree
096                    lir.getControlFlowGraph().getBlocks().forEach(this::analyzeBlock);
097                    // remove all with only one use
098                    map.filter(t -> {
099                        if (t.usageCount() > 1) {
100                            return true;
101                        } else {
102                            singleUsageConstantsSkipped.increment();
103                            return false;
104                        }
105                    });
106                    // collect block map
107                    map.forEach(tree -> tree.forEach(this::addUsageToBlockMap));
108                } catch (Throwable e) {
109                    throw Debug.handle(e);
110                }
111
112                try (Scope s = Debug.scope("BuildConstantTree")) {
113                    // create ConstantTree
114                    map.forEach(this::createConstantTree);
115
116                    // insert moves, delete null instructions and reset instruction ids
117                    lir.getControlFlowGraph().getBlocks().forEach(this::rewriteBlock);
118
119                    assert verifyStates();
120                } catch (Throwable e) {
121                    throw Debug.handle(e);
122                }
123            }
124        }
125
126        private boolean verifyStates() {
127            map.forEach(this::verifyStateUsage);
128            return true;
129        }
130
131        private void verifyStateUsage(DefUseTree tree) {
132            Variable var = tree.getVariable();
133            ValueConsumer stateConsumer = new ValueConsumer() {
134
135                @Override
136                public void visitValue(Value operand, OperandMode mode, EnumSet<OperandFlag> flags) {
137                    assert !operand.equals(var) : "constant usage through variable in frame state " + var;
138                }
139            };
140            for (AbstractBlockBase<?> block : lir.getControlFlowGraph().getBlocks()) {
141                for (LIRInstruction inst : lir.getLIRforBlock(block)) {
142                    // set instruction id to the index in the lir instruction list
143                    inst.visitEachState(stateConsumer);
144                }
145            }
146        }
147
148        private static boolean isConstantLoad(LIRInstruction inst) {
149            if (!(inst instanceof MoveOp)) {
150                return false;
151            }
152            MoveOp move = (MoveOp) inst;
153            return isConstant(move.getInput()) && isVariable(move.getResult());
154        }
155
156        private void addUsageToBlockMap(UseEntry entry) {
157            AbstractBlockBase<?> block = entry.getBlock();
158            List<UseEntry> list = blockMap.get(block);
159            if (list == null) {
160                list = new ArrayList<>();
161                blockMap.put(block, list);
162            }
163            list.add(entry);
164        }
165
166        /**
167         * Collects def-use information for a {@code block}.
168         */
169        private void analyzeBlock(AbstractBlockBase<?> block) {
170            try (Indent indent = Debug.logAndIndent("Block: %s", block)) {
171
172                InstructionValueConsumer loadConsumer = (instruction, value, mode, flags) -> {
173                    if (isVariable(value)) {
174                        Variable var = (Variable) value;
175
176                        if (!phiConstants.get(var.index)) {
177                            if (!defined.get(var.index)) {
178                                defined.set(var.index);
179                                if (isConstantLoad(instruction)) {
180                                    Debug.log("constant load: %s", instruction);
181                                    map.put(var, new DefUseTree(instruction, block));
182                                    constantsTotal.increment();
183                                }
184                            } else {
185                                // Variable is redefined, this only happens for constant loads
186                                // introduced by phi resolution -> ignore.
187                                DefUseTree removed = map.remove(var);
188                                if (removed != null) {
189                                    phiConstantsSkipped.increment();
190                                }
191                                phiConstants.set(var.index);
192                                Debug.log(3, "Removing phi variable: %s", var);
193                            }
194                        } else {
195                            assert defined.get(var.index) : "phi but not defined? " + var;
196                        }
197                    }
198                };
199
200                InstructionValueConsumer useConsumer = (instruction, value, mode, flags) -> {
201                    if (isVariable(value)) {
202                        Variable var = (Variable) value;
203                        if (!phiConstants.get(var.index)) {
204                            DefUseTree tree = map.get(var);
205                            if (tree != null) {
206                                tree.addUsage(block, instruction, value);
207                                Debug.log("usage of %s : %s", var, instruction);
208                            }
209                        }
210                    }
211                };
212
213                int opId = 0;
214                for (LIRInstruction inst : lir.getLIRforBlock(block)) {
215                    // set instruction id to the index in the lir instruction list
216                    inst.setId(opId++);
217                    inst.visitEachOutput(loadConsumer);
218                    inst.visitEachInput(useConsumer);
219                    inst.visitEachAlive(useConsumer);
220
221                }
222            }
223        }
224
225        /**
226         * Creates the dominator tree and searches for an solution.
227         */
228        private void createConstantTree(DefUseTree tree) {
229            ConstantTree constTree = new ConstantTree(lir.getControlFlowGraph(), tree);
230            constTree.set(Flags.SUBTREE, tree.getBlock());
231            tree.forEach(u -> constTree.set(Flags.USAGE, u.getBlock()));
232
233            if (constTree.get(Flags.USAGE, tree.getBlock())) {
234                // usage in the definition block -> no optimization
235                usageAtDefinitionSkipped.increment();
236                return;
237            }
238
239            constTree.markBlocks();
240
241            NodeCost cost = ConstantTreeAnalyzer.analyze(constTree, tree.getBlock());
242            int usageCount = cost.getUsages().size();
243            assert usageCount == tree.usageCount() : "Usage count differs: " + usageCount + " vs. " + tree.usageCount();
244
245            if (Debug.isLogEnabled()) {
246                try (Indent i = Debug.logAndIndent("Variable: %s, Block: %s, prob.: %f", tree.getVariable(), tree.getBlock(), tree.getBlock().probability())) {
247                    Debug.log("Usages result: %s", cost);
248                }
249
250            }
251
252            if (cost.getNumMaterializations() > 1 || cost.getBestCost() < tree.getBlock().probability()) {
253                try (Scope s = Debug.scope("CLOmodify", constTree); Indent i = Debug.logAndIndent("Replacing %s = %s", tree.getVariable(), tree.getConstant().toValueString())) {
254                    // mark original load for removal
255                    deleteInstruction(tree);
256                    constantsOptimized.increment();
257
258                    // collect result
259                    createLoads(tree, constTree, tree.getBlock());
260
261                } catch (Throwable e) {
262                    throw Debug.handle(e);
263                }
264            } else {
265                // no better solution found
266                materializeAtDefinitionSkipped.increment();
267            }
268            Debug.dump(constTree, "ConstantTree for " + tree.getVariable());
269        }
270
271        private void createLoads(DefUseTree tree, ConstantTree constTree, AbstractBlockBase<?> startBlock) {
272            Deque<AbstractBlockBase<?>> worklist = new ArrayDeque<>();
273            worklist.add(startBlock);
274            while (!worklist.isEmpty()) {
275                AbstractBlockBase<?> block = worklist.pollLast();
276                if (constTree.get(Flags.CANDIDATE, block)) {
277                    constTree.set(Flags.MATERIALIZE, block);
278                    // create and insert load
279                    insertLoad(tree.getConstant(), tree.getVariable().getLIRKind(), block, constTree.getCost(block).getUsages());
280                } else {
281                    for (AbstractBlockBase<?> dominated : block.getDominated()) {
282                        if (constTree.isMarked(dominated)) {
283                            worklist.addLast(dominated);
284                        }
285                    }
286                }
287            }
288        }
289
290        private void insertLoad(JavaConstant constant, LIRKind kind, AbstractBlockBase<?> block, List<UseEntry> usages) {
291            assert usages != null && usages.size() > 0 : String.format("No usages %s %s %s", constant, block, usages);
292            // create variable
293            Variable variable = lirGen.newVariable(kind);
294            // create move
295            LIRInstruction move = lirGen.getSpillMoveFactory().createMove(variable, constant);
296            // insert instruction
297            getInsertionBuffer(block).append(1, move);
298            Debug.log("new move (%s) and inserted in block %s", move, block);
299            // update usages
300            for (UseEntry u : usages) {
301                u.setValue(variable);
302                Debug.log("patched instruction %s", u.getInstruction());
303            }
304        }
305
306        /**
307         * Inserts the constant loads created in {@link #createConstantTree} and deletes the
308         * original definition.
309         */
310        private void rewriteBlock(AbstractBlockBase<?> block) {
311            // insert moves
312            LIRInsertionBuffer buffer = insertionBuffers.get(block);
313            if (buffer != null) {
314                assert buffer.initialized() : "not initialized?";
315                buffer.finish();
316            }
317
318            // delete instructions
319            List<LIRInstruction> instructions = lir.getLIRforBlock(block);
320            boolean hasDead = false;
321            for (LIRInstruction inst : instructions) {
322                if (inst == null) {
323                    hasDead = true;
324                } else {
325                    inst.setId(-1);
326                }
327            }
328            if (hasDead) {
329                // Remove null values from the list.
330                instructions.removeAll(Collections.singleton(null));
331            }
332        }
333
334        private void deleteInstruction(DefUseTree tree) {
335            AbstractBlockBase<?> block = tree.getBlock();
336            LIRInstruction instruction = tree.getInstruction();
337            Debug.log("deleting instruction %s from block %s", instruction, block);
338            lir.getLIRforBlock(block).set(instruction.id(), null);
339        }
340
341        private LIRInsertionBuffer getInsertionBuffer(AbstractBlockBase<?> block) {
342            LIRInsertionBuffer insertionBuffer = insertionBuffers.get(block);
343            if (insertionBuffer == null) {
344                insertionBuffer = new LIRInsertionBuffer();
345                insertionBuffers.put(block, insertionBuffer);
346                assert !insertionBuffer.initialized() : "already initialized?";
347                List<LIRInstruction> instructions = lir.getLIRforBlock(block);
348                insertionBuffer.init(instructions);
349            }
350            return insertionBuffer;
351        }
352    }
353}