1 /*
2 * Copyright (c) 2026, 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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 import jdk.incubator.code.Block;
25 import jdk.incubator.code.CodeTransformer;
26 import jdk.incubator.code.Op;
27 import jdk.incubator.code.dialect.core.CoreOp;
28 import jdk.incubator.code.dialect.core.CoreOp.*;
29 import jdk.incubator.code.dialect.java.JavaOp.*;
30
31 /// Normalizes lifted fragments of the same exception region
32 ///
33 /// ```
34 /// entry
35 /// |
36 /// enter R#1 handler H
37 /// / \ entry
38 /// protected A protected B |
39 /// | | enter R handler H
40 /// exit R#1 exit R#1 / \
41 /// | | -> protected A protected B
42 /// branch adapter branch adapter \ /
43 /// \ / protected C
44 /// enter R#2 handler H |
45 /// | exit R
46 /// protected C
47 /// |
48 /// exit R#2
49 /// ```
50 final class NormalizeExceptionRegionsTransformer implements CodeTransformer {
51
52 private boolean modified;
53
54 static FuncOp transform(FuncOp func) {
55 var t = new NormalizeExceptionRegionsTransformer();
56 do {
57 // Inner regions are exposed after the outer are collapsed
58 t.modified = false;
59 func = func.transform(t);
60 } while (t.modified);
61 return func;
62 }
63
64 @Override
65 public void acceptBlock(Block.Builder builder, Block b) {
66 // Skip blocks whose terminating op was mapped by a previous collapse
67 if (!builder.context().queryValue(b.terminatingOp().result()).isPresent()) {
68 CodeTransformer.super.acceptBlock(builder, b);
69 }
70 }
71
72 @Override
73 public Block.Builder acceptOp(Block.Builder builder, Op op) {
74 // Collapse matching exit/re-enter boundaries
75 if (!(op instanceof ExceptionRegionExit exit) || !collapseExitToReenter(builder, exit)) {
76 builder.add(op);
77 }
78 return builder;
79 }
80
81 private boolean collapseExitToReenter(Block.Builder builder, ExceptionRegionExit exit) {
82 // Match exit -> optional branch adapter -> re-enter
83 ExceptionRegionEnter exitedEnter = exit.enterOp();
84 Block exitTarget = exit.endReference().targetBlock();
85 BranchOp adapterBranch = asBranchAdapter(exitTarget);
86 Block enterBlock = adapterBranch == null ? exitTarget : adapterBranch.branch().targetBlock();
87 if (enterBlock.ops().size() != 1
88 || !(enterBlock.terminatingOp() instanceof ExceptionRegionEnter reenter)
89 || !isExitTargetOf(exitTarget, exitedEnter)
90 || (adapterBranch != null && !isOnlyReachedByExitAdapters(enterBlock, exitedEnter))
91 || !sameCatchTargets(exitedEnter, reenter)
92 || !reenter.result().isDominatedBy(exitedEnter.result())) {
93 return false;
94 }
95 // Reuse the old enter result and skip the adapter blocks
96 var cc = builder.context();
97 cc.mapValue(reenter.result(), cc.getValue(exitedEnter.result()));
98 cc.mapValues(exitTarget.parameters(), cc.getValues(exit.endReference().arguments()));
99 if (adapterBranch != null) {
100 cc.mapValues(enterBlock.parameters(), cc.getValues(adapterBranch.branch().arguments()));
101 }
102 Op.Result result = builder.add(CoreOp.branch(cc.getReferenceOrCreate(reenter.startReference())));
103 if (adapterBranch != null) {
104 cc.mapValue(adapterBranch.result(), result);
105 }
106 modified = true;
107 return true;
108 }
109
110 // Return a branch adapter, if this block is one
111 private static BranchOp asBranchAdapter(Block block) {
112 return block.ops().size() == 1 && block.terminatingOp() instanceof BranchOp branch
113 ? branch
114 : null;
115 }
116
117 // All predecessors must exit the same enter
118 private static boolean isExitTargetOf(Block block, ExceptionRegionEnter enter) {
119 return !block.predecessors().isEmpty()
120 && block.predecessors().stream().allMatch(predecessor ->
121 predecessor.terminatingOp() instanceof ExceptionRegionExit e && e.enterOp() == enter);
122 }
123
124 // Several branch adapters may feed the same re-enter block
125 private static boolean isOnlyReachedByExitAdapters(Block block, ExceptionRegionEnter enter) {
126 return block.predecessors().stream().allMatch(predecessor ->
127 predecessor.terminatingOp() instanceof BranchOp branch && isExitTargetOf(predecessor, enter));
128 }
129
130 // Compare real handler targets
131 private static boolean sameCatchTargets(ExceptionRegionEnter first, ExceptionRegionEnter second) {
132 return first.catchReferences().stream().map(NormalizeExceptionRegionsTransformer::skipBranchAdapter).toList()
133 .equals(second.catchReferences().stream().map(NormalizeExceptionRegionsTransformer::skipBranchAdapter).toList());
134 }
135
136 // Skip one branch adapter
137 private static Block skipBranchAdapter(Block.Reference reference) {
138 Block target = reference.targetBlock();
139 BranchOp adapterBranch = asBranchAdapter(target);
140 return adapterBranch != null
141 ? adapterBranch.branch().targetBlock()
142 : target;
143 }
144 }