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.traverse(null, CodeElement.opVisitor((_, op) -> {
121 // Verify operands declaration dominannce
122 for (var v : op.operands()) {
123 if (!op.result().isDominatedBy(v)) {
124 error("%s %s operand %s is not dominated by its declaration in %s", op.ancestorBlock(), op, v, v.declaringBlock());
125 }
126 }
127
128 // Verify individual Ops
129 switch (op) {
130 case CoreOp.BranchOp br ->
131 verifyBlockReferences(op, br.successors());
132 case CoreOp.ConditionalBranchOp cbr ->
133 verifyBlockReferences(op, cbr.successors());
134 case JavaOp.ArithmeticOperation _, JavaOp.TestOperation _ ->
135 verifyOpHandleExists(op, op.externalizeOpName());
136 case JavaOp.ConvOp _ -> {
137 verifyOpHandleExists(op, op.externalizeOpName() + "_" + op.opType().returnType());
138 }
139 default -> {}
140
141 }
142 return null;
143 }));
144 }
145
146 private void verifyBlockReferences(Op op, List<Block.Reference> references) {
147 for (Block.Reference r : references) {
148 Block b = r.targetBlock();
149 List<Value> args = r.arguments();
150 List<Block.Parameter> params = r.targetBlock().parameters();
151 if (args.size() != params.size()) {
152 error("%s %s block reference arguments size to target block parameters size mismatch", b, op);
153 } else {
154 Block tb = r.targetBlock();
155 for (int i = 0; i < args.size(); i++) {
156 if (!isAssignable(params.get(i).type(), args.get(i), tb, b)) {
157 error("%s %s %s is not assignable from %s", op.ancestorBlock(), op, params.get(i).type(), args.get(i).type());
158 }
159 }
160 }
161 }
162 }
163
164 private boolean isAssignable(TypeElement toType, Value fromValue, Object toContext, Object fromContext) {
165 if (toType.equals(fromValue.type())) return true;
166 var to = resolveToClass(toType, toContext);
167 var from = resolveToClass(fromValue.type(), fromContext);
168 if (from.isPrimitive()) {
169 // Primitive types assignability
170 return to == int.class && (from == byte.class || from == short.class || from == char.class);
171 } else {
172 // Objects assignability
173 return to.isAssignableFrom(from);
174 }
175 }
176
177 public Class<?> resolveToClass(TypeElement d, Object context) {
178 try {
179 if (d instanceof JavaType jt) {
180 return (Class<?>)jt.erasure().resolve(lookup);
181 } else {
182 error("%s %s is not a Java type", context, d);
183 }
184 } catch (ReflectiveOperationException e) {
185 error("%s %s", context, e.getMessage());
186 }
187 return Object.class;
188 }
189
190 static final Class<?> CLASS_INVOKABLE_LEAF_OPS;
191 static {
192 try {
193 CLASS_INVOKABLE_LEAF_OPS = Class.forName("jdk.incubator.code.interpreter.InvokableLeafOps");
194 } catch (ReflectiveOperationException roe) {
195 throw new InternalError(roe);
196 }
197 }
198
199 private void verifyOpHandleExists(Op op, String opName) {
200 try {
201 var mt = MethodRef.toNominalDescriptor(op.opType()).resolveConstantDesc(lookup).erase();
202 CLASS_INVOKABLE_LEAF_OPS.getDeclaredMethod(opName, mt.parameterArray());
203 } catch (NoSuchMethodException nsme) {
204 error("%s %s of type %s is not supported", op.ancestorBlock(), op, op.opType());
205 } catch (ReflectiveOperationException roe) {
206 error("%s %s %s", op.ancestorBlock(), op, roe.getMessage());
207 }
208 }
209
210 private void verifyExceptionRegions() {
211 rootOp.traverse(new HashMap<Block, List<Block>>(), CodeElement.blockVisitor((map, b) -> {
212 List<Block> catchBlocks = map.computeIfAbsent(b, _ -> List.of());
213 switch (b.terminatingOp()) {
214 case CoreOp.BranchOp br ->
215 verifyCatchStack(b, br, br.branch(), catchBlocks, map);
216 case CoreOp.ConditionalBranchOp cbr -> {
217 verifyCatchStack(b, cbr, cbr.trueBranch(), catchBlocks, map);
218 verifyCatchStack(b, cbr, cbr.falseBranch(), catchBlocks, map);
219 }
220 case JavaOp.ExceptionRegionEnter ere -> {
221 List<Block> newCatchBlocks = new ArrayList<>();
222 newCatchBlocks.addAll(catchBlocks);
223 for (Block.Reference cb : ere.catchBlocks()) {
224 newCatchBlocks.add(cb.targetBlock());
225 verifyCatchStack(b, ere, cb, catchBlocks, map);
226 }
227 verifyCatchStack(b, ere, ere.start(), newCatchBlocks, map);
228 }
229 case JavaOp.ExceptionRegionExit ere -> {
230 List<Block> exitedCatchBlocks = ere.catchBlocks().stream().map(Block.Reference::targetBlock).toList();
231 if (exitedCatchBlocks.size() > catchBlocks.size() || !catchBlocks.reversed().subList(0, exitedCatchBlocks.size()).equals(exitedCatchBlocks)) {
232 error("%s %s exited catch blocks %s does not match actual stack %s", b, ere, exitedCatchBlocks, catchBlocks);
233 } else {
234 verifyCatchStack(b, ere, ere.end(), catchBlocks.subList(0, catchBlocks.size() - exitedCatchBlocks.size()), map);
235 }
236 }
237 default -> {}
238 }
239 return map;
240 }));
241 }
242
243 private void verifyCatchStack(Block b, Op op, Block.Reference target, List<Block> catchBlocks, Map<Block, List<Block>> blockMap) {
244 blockMap.compute(target.targetBlock(), (tb, stored) -> {
245 if (stored != null && !stored.equals(catchBlocks)) {
246 error("%s %s catch stack mismatch at target %s %s vs %s", b, op, tb, stored, catchBlocks);
247 }
248 return catchBlocks;
249 });
250 }
251 }