1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package jdk.incubator.code.analysis;
 27 
 28 import jdk.incubator.code.*;
 29 import jdk.incubator.code.dialect.core.CoreOp;
 30 import jdk.incubator.code.dialect.java.JavaOp;
 31 
 32 import java.util.*;
 33 
 34 /**
 35  * A model transformer that normalizes blocks.
 36  * <p>
 37  * Merges redundant blocks with their predecessors, those which are unconditionally
 38  * branched to and have only one predecessor.
 39  * <p>
 40  * Removes unused block parameters.
 41  */
 42 public final class NormalizeBlocksTransformer implements OpTransformer {
 43     final Set<Block> mergedBlocks = new HashSet<>();
 44     final Map<Block, BitSet> adjustedBlocks = new HashMap<>();
 45 
 46     private NormalizeBlocksTransformer() {
 47     }
 48 
 49     /**
 50      * Transforms an operation, merging redundant blocks.
 51      *
 52      * @param op  the operation to transform
 53      * @param <O> the type of operation
 54      * @return the transformed operation
 55      */
 56     @SuppressWarnings("unchecked")
 57     public static <O extends Op> O transform(O op) {
 58         return (O) op.transform(CopyContext.create(), new NormalizeBlocksTransformer());
 59     }
 60 
 61     ;
 62 
 63     @Override
 64     public void apply(Block.Builder block, Block b) {
 65         // Ignore merged block
 66         if (!mergedBlocks.contains(b)) {
 67             OpTransformer.super.apply(block, b);
 68         }
 69     }
 70 
 71     @Override
 72     public Block.Builder apply(Block.Builder b, Op op) {
 73         if (op instanceof CoreOp.BranchOp bop &&
 74                 bop.branch().targetBlock().predecessors().size() == 1) {
 75             // Merge the successor's target block with this block, and so on
 76             // The terminal branch operation is replaced with the operations in the
 77             // successor's target block
 78             mergeBlock(b, bop);
 79             return b;
 80         } else if (op instanceof JavaOp.ExceptionRegionEnter ere) {
 81             // Cannot remove block parameters from exception handlers
 82             removeUnusedBlockParameters(b, ere.start());
 83         } else if (op instanceof JavaOp.ExceptionRegionExit ere) {
 84             // Cannot remove block parameters from exception handlers
 85             removeUnusedBlockParameters(b, ere.end());
 86         } else if (op instanceof Op.BlockTerminating) {
 87             for (Block.Reference successor : op.successors()) {
 88                 removeUnusedBlockParameters(b, successor);
 89             }
 90         }
 91         b.op(op);
 92         return b;
 93     }
 94 
 95     // Remove any unused block parameters and successor arguments
 96     private void removeUnusedBlockParameters(Block.Builder b, Block.Reference successor) {
 97         Block target = successor.targetBlock();
 98         BitSet unusedParameterIndexes = adjustedBlocks.computeIfAbsent(target,
 99                 k -> adjustBlock(b, k));
100         if (!unusedParameterIndexes.isEmpty()) {
101             adjustSuccessor(unusedParameterIndexes, b, successor);
102         }
103 
104     }
105 
106     // Remove any unused block parameters
107     BitSet adjustBlock(Block.Builder b, Block target) {
108         // Determine the indexes of unused block parameters
109         List<Block.Parameter> parameters = target.parameters();
110         BitSet unusedParameterIndexes = parameters.stream()
111                 .filter(p -> p.uses().isEmpty())
112                 .mapToInt(Block.Parameter::index)
113                 .collect(BitSet::new, BitSet::set, BitSet::or);
114 
115         if (!unusedParameterIndexes.isEmpty()) {
116             // Create a new output block and remap it to the target block,
117             // overriding any previous mapping
118             Block.Builder adjustedBlock = b.block();
119             b.context().mapBlock(target, adjustedBlock);
120 
121             // Update and remap the output block parameters
122             for (int i = 0; i < parameters.size(); i++) {
123                 if (!unusedParameterIndexes.get(i)) {
124                     Block.Parameter parameter = parameters.get(i);
125                     b.context().mapValue(
126                             parameter,
127                             adjustedBlock.parameter(parameter.type()));
128                 }
129             }
130         }
131 
132         return unusedParameterIndexes;
133     }
134 
135     // Remove any unused successor arguments
136     void adjustSuccessor(BitSet unusedParameterIndexes, Block.Builder b, Block.Reference successor) {
137         // Create a new output successor and remap it
138         List<Value> arguments = new ArrayList<>();
139         for (int i = 0; i < successor.arguments().size(); i++) {
140             if (!unusedParameterIndexes.get(i)) {
141                 arguments.add(b.context().getValue(successor.arguments().get(i)));
142             }
143         }
144         Block.Reference adjustedSuccessor = b.context().getBlock(successor.targetBlock())
145                 .successor(arguments);
146         b.context().mapSuccessor(successor, adjustedSuccessor);
147     }
148 
149     void mergeBlock(Block.Builder b, CoreOp.BranchOp bop) {
150         Block.Reference reference = bop.branch();
151         Block successor = reference.targetBlock();
152         // Replace use of the successor's parameters with the reference's arguments
153         b.context().mapValues(successor.parameters(),
154                 b.context().getValues(reference.arguments()));
155         mergeBlock(b, successor);
156     }
157 
158     void mergeBlock(Block.Builder b, Block successor) {
159         mergedBlocks.add(successor);
160 
161         // Merge non-terminal operations
162         for (int i = 0; i < successor.ops().size() - 1; i++) {
163             b.op(successor.ops().get(i));
164         }
165 
166         // Check if subsequent successor block can be normalized
167         apply(b, successor.terminatingOp());
168     }
169 }