1 /*
2 * Copyright (c) 2024, 2025, 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 java.lang.invoke.MethodHandles;
25 import jdk.incubator.code.*;
26 import jdk.incubator.code.dialect.core.CoreOp;
27 import jdk.incubator.code.dialect.java.JavaOp;
28 import jdk.incubator.code.dialect.java.JavaType;
29 import jdk.incubator.code.dialect.java.MethodRef;
30 import jdk.incubator.code.extern.OpWriter;
31
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37
38 public final class Verifier {
39
40 public final class VerifyError {
41
42 private final String message;
43
44 public VerifyError(String message) {
45 this.message = message;
46 }
47
48 public String getMessage() {
49 return message;
50 }
51
52 public String getPrintedContext() {
53 return toText(rootOp);
54 }
55
56 @Override
57 public String toString() {
58 return getMessage() + " in " + getPrintedContext();
59 }
60 }
61
62 public static List<Verifier.VerifyError> verify(Op op) {
63 return verify(MethodHandles.publicLookup(), op);
64 }
65
66 public static List<Verifier.VerifyError> verify(MethodHandles.Lookup l, Op op) {
67 var verifier = new Verifier(l, op);
68 verifier.verifyOps();
69 verifier.verifyExceptionRegions();
70 return verifier.errors == null ? List.of() : Collections.unmodifiableList(verifier.errors);
71 }
72
73
74 private final MethodHandles.Lookup lookup;
75 private final Op rootOp;
76 private OpWriter.CodeItemNamerOption namerOption;
77 private List<Verifier.VerifyError> errors;
78
79 private Verifier(MethodHandles.Lookup lookup, Op rootOp) {
80 this.lookup = lookup;
81 this.rootOp = rootOp;
82 }
83
84 private OpWriter.CodeItemNamerOption getNamer() {
85 if (namerOption == null) {
86 namerOption = OpWriter.CodeItemNamerOption.of(OpWriter.computeGlobalNames(rootOp));
87 }
88 return namerOption;
89 }
90
91 private String toText(Op op) {
92 return OpWriter.toText(op, getNamer());
93 }
94
95 private String getName(CodeItem codeItem) {
96 return getNamer().namer().apply(codeItem);
97 }
98
99 private void error(String message, Object... args) {
100 if (errors == null) {
101 errors = new ArrayList<>();
102 }
103 for (int i = 0; i < args.length; i++) {
104 args[i] = toText(args[i]);
105 }
106 errors.add(new VerifyError(message.formatted(args)));
107 }
108
109 private String toText(Object arg) {
110 return switch (arg) {
111 case Op op -> toText(op);
112 case Block b -> getName(b);
113 case Value v -> getName(v);
114 case List<?> l -> l.stream().map(this::toText).toList().toString();
115 default -> arg.toString();
116 };
117 }
118
119 private void verifyOps() {
120 rootOp.elements().forEach(e -> {
121 if (!(e instanceof Op op)) {
122 return;
123 }
124
125 // Verify operands declaration dominance
126 for (var v : op.operands()) {
127 if (!op.result().isDominatedBy(v)) {
128 error("%s %s operand %s is not dominated by its declaration in %s", op.ancestorBlock(), op, v, v.declaringBlock());
129 }
130 }
131
132 // Verify individual Ops
133 switch (op) {
134 case CoreOp.BranchOp br ->
135 verifyBlockReferences(op, br.successors());
136 case CoreOp.ConditionalBranchOp cbr ->
137 verifyBlockReferences(op, cbr.successors());
138 case JavaOp.ArithmeticOperation _, JavaOp.TestOperation _ ->
139 verifyOpHandleExists(op, op.externalizeOpName());
140 case JavaOp.ConvOp _ -> {
141 verifyOpHandleExists(op, op.externalizeOpName() + "_" + op.opType().returnType());
142 }
143 default -> {}
144
145 }
146 });
147 }
148
149 private void verifyBlockReferences(Op op, List<Block.Reference> references) {
150 for (Block.Reference r : references) {
151 Block b = r.targetBlock();
152 List<Value> args = r.arguments();
153 List<Block.Parameter> params = r.targetBlock().parameters();
154 if (args.size() != params.size()) {
155 error("%s %s block reference arguments size to target block parameters size mismatch", b, op);
156 } else {
157 Block tb = r.targetBlock();
158 for (int i = 0; i < args.size(); i++) {
159 if (!isAssignable(params.get(i).type(), args.get(i), tb, b)) {
160 error("%s %s %s is not assignable from %s", op.ancestorBlock(), op, params.get(i).type(), args.get(i).type());
161 }
162 }
163 }
164 }
165 }
166
167 private boolean isAssignable(TypeElement toType, Value fromValue, Object toContext, Object fromContext) {
168 if (toType.equals(fromValue.type())) return true;
169 var to = resolveToClass(toType, toContext);
170 var from = resolveToClass(fromValue.type(), fromContext);
171 if (from.isPrimitive()) {
172 // Primitive types assignability
173 return to == int.class && (from == byte.class || from == short.class || from == char.class);
174 } else {
175 // Objects assignability
176 return to.isAssignableFrom(from);
177 }
178 }
179
180 public Class<?> resolveToClass(TypeElement d, Object context) {
181 try {
182 if (d instanceof JavaType jt) {
183 return (Class<?>)jt.erasure().resolve(lookup);
184 } else {
185 error("%s %s is not a Java type", context, d);
186 }
187 } catch (ReflectiveOperationException e) {
188 error("%s %s", context, e.getMessage());
189 }
190 return Object.class;
191 }
192
193 static final Class<?> CLASS_INVOKABLE_LEAF_OPS;
194 static {
195 try {
196 CLASS_INVOKABLE_LEAF_OPS = Class.forName("jdk.incubator.code.interpreter.InvokableLeafOps");
197 } catch (ReflectiveOperationException roe) {
198 throw new InternalError(roe);
199 }
200 }
201
202 private void verifyOpHandleExists(Op op, String opName) {
203 try {
204 var mt = MethodRef.toNominalDescriptor(op.opType()).resolveConstantDesc(lookup).erase();
205 CLASS_INVOKABLE_LEAF_OPS.getDeclaredMethod(opName, mt.parameterArray());
206 } catch (NoSuchMethodException nsme) {
207 error("%s %s of type %s is not supported", op.ancestorBlock(), op, op.opType());
208 } catch (ReflectiveOperationException roe) {
209 error("%s %s %s", op.ancestorBlock(), op, roe.getMessage());
210 }
211 }
212
213 private void verifyExceptionRegions() {
214 Map<Block, List<Block>> map = new HashMap<>();
215 rootOp.elements().forEach(e -> {
216 if (!(e instanceof Block b)) {
217 return;
218 }
219 List<Block> catchBlocks = map.computeIfAbsent(b, _ -> List.of());
220 switch (b.terminatingOp()) {
221 case CoreOp.BranchOp br ->
222 verifyCatchStack(b, br, br.branch(), catchBlocks, map);
223 case CoreOp.ConditionalBranchOp cbr -> {
224 verifyCatchStack(b, cbr, cbr.trueBranch(), catchBlocks, map);
225 verifyCatchStack(b, cbr, cbr.falseBranch(), catchBlocks, map);
226 }
227 case JavaOp.ExceptionRegionEnter ere -> {
228 List<Block> newCatchBlocks = new ArrayList<>();
229 newCatchBlocks.addAll(catchBlocks);
230 for (Block.Reference cb : ere.catchBlocks()) {
231 newCatchBlocks.add(cb.targetBlock());
232 verifyCatchStack(b, ere, cb, catchBlocks, map);
233 }
234 verifyCatchStack(b, ere, ere.start(), newCatchBlocks, map);
235 }
236 case JavaOp.ExceptionRegionExit ere -> {
237 List<Block> exitedCatchBlocks = ere.catchBlocks().stream().map(Block.Reference::targetBlock).toList();
238 if (exitedCatchBlocks.size() > catchBlocks.size() || !catchBlocks.reversed().subList(0, exitedCatchBlocks.size()).equals(exitedCatchBlocks)) {
239 error("%s %s exited catch blocks %s does not match actual stack %s", b, ere, exitedCatchBlocks, catchBlocks);
240 } else {
241 verifyCatchStack(b, ere, ere.end(), catchBlocks.subList(0, catchBlocks.size() - exitedCatchBlocks.size()), map);
242 }
243 }
244 default -> {}
245 }
246 });
247 }
248
249 private void verifyCatchStack(Block b, Op op, Block.Reference target, List<Block> catchBlocks, Map<Block, List<Block>> blockMap) {
250 blockMap.compute(target.targetBlock(), (tb, stored) -> {
251 if (stored != null && !stored.equals(catchBlocks)) {
252 error("%s %s catch stack mismatch at target %s %s vs %s", b, op, tb, stored, catchBlocks);
253 }
254 return catchBlocks;
255 });
256 }
257 }