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.
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.Body;
26 import jdk.incubator.code.Reflect;
27 import jdk.incubator.code.Op;
28 import jdk.incubator.code.analysis.SSA;
29 import jdk.incubator.code.dialect.java.JavaOp;
30 import org.junit.jupiter.api.Assertions;
31 import org.junit.jupiter.api.Test;
32
33 import java.util.function.IntBinaryOperator;
34
35 import static jdk.incubator.code.dialect.core.CoreOp.*;
36 import static jdk.incubator.code.dialect.core.CoreType.FUNCTION_TYPE_VOID;
37 import static jdk.incubator.code.dialect.core.CoreType.functionType;
38 import static jdk.incubator.code.dialect.java.JavaType.INT;
39 import static jdk.incubator.code.dialect.java.JavaType.type;
40
41 /*
42 * @test
43 * @modules jdk.incubator.code
44 * @run junit TestBuild
45 * @run junit/othervm -Dbabylon.ssa=cytron TestBuild
46 */
47
48 public class TestBuild {
49
50 public JavaOp.LambdaOp f() {
51 IntBinaryOperator ibo = (@Reflect IntBinaryOperator) (a, b) -> a + b;
52 return SSA.transform((JavaOp.LambdaOp) Op.ofQuotable(ibo).get().op());
53 }
54
55 @Test
56 public void testBoundValueAsOperand() {
57 JavaOp.LambdaOp f = f();
58
59 var body = Body.Builder.of(null, f.invokableType());
60 var block = body.entryBlock();
61
62 var a = f.body().entryBlock().parameters().get(0);
63 var b = f.body().entryBlock().parameters().get(1);
64 // Passing bound values as operands to a new unbound operation
65 var addop = JavaOp.add(a, b);
66
67 Assertions.assertThrows(IllegalStateException.class, () -> block.op(addop));
68 }
69
70 @Test
71 public void testBoundValueAsHeaderArgument() {
72 JavaOp.LambdaOp f = f();
73
74 var body = Body.Builder.of(null, f.invokableType());
75 var block = body.entryBlock();
76 var anotherBlock = block.block(INT, INT);
77
78 var a = f.body().entryBlock().parameters().get(0);
79 var b = f.body().entryBlock().parameters().get(1);
80 // Passing bound values as header arguments of a header
81 // that is the successor of a terminal operation
82 var brop = branch(anotherBlock.successor(a, b));
83
84 Assertions.assertThrows(IllegalStateException.class, () -> block.op(brop));
85 }
86
87 @Test
88 public void testUnmappedBoundValue() {
89 JavaOp.LambdaOp f = f();
90
91 var body = Body.Builder.of(null, f.invokableType());
92 var block = body.entryBlock();
93
94 var freturnOp = f.body().entryBlock().terminatingOp();
95 // Unmapped bound value that is operand of the bound return op
96 Assertions.assertThrows(IllegalArgumentException.class, () -> block.op(freturnOp));
97 }
98
99 @Test
100 public void testMappingToBoundValue() {
101 JavaOp.LambdaOp f = f();
102
103 var body = Body.Builder.of(null, f.invokableType());
104 var block = body.entryBlock();
105
106 var result = f.body().entryBlock().firstOp().result();
107 // Mapping to a bound value
108 Assertions.assertThrows(IllegalArgumentException.class, () -> block.context().mapValue(result, result));
109 }
110
111 @Test
112 public void testMappedBoundValue() {
113 JavaOp.LambdaOp f = f();
114
115 var body = Body.Builder.of(null, f.invokableType());
116 var block = body.entryBlock();
117
118 var a = block.parameters().get(0);
119 var b = block.parameters().get(1);
120 var result = block.op(JavaOp.add(a, b));
121 // Map the bound value used as the operand to the bound return op to
122 // the above value
123 block.context().mapValue(f.body().entryBlock().firstOp().result(), result);
124
125 var freturnOp = f.body().entryBlock().terminatingOp();
126 // No error since values (operands) are mapped
127 block.op(freturnOp);
128 }
129
130 @Test
131 public void testPartiallyConstructedValueAccess() {
132 var body = Body.Builder.of(null, functionType(INT, INT, INT));
133 var block = body.entryBlock();
134
135 Block.Parameter a = block.parameters().get(0);
136 Block.Parameter b = block.parameters().get(1);
137 Op.Result result = block.op(JavaOp.add(a, b));
138
139 // Access the declaring block of values before the block and its body are
140 // constructed
141 Assertions.assertThrows(IllegalStateException.class, a::declaringBlock);
142 Assertions.assertThrows(IllegalStateException.class, result::declaringBlock);
143 // Access to parent block/body of operation result before they are constructed
144 Assertions.assertThrows(IllegalStateException.class, result.op()::ancestorBlock);
145 Assertions.assertThrows(IllegalStateException.class, result.op()::ancestorBody);
146 // Access to set of users before constructed
147 Assertions.assertThrows(IllegalStateException.class, a::uses);
148
149 block.op(return_(result));
150
151 var f = func("f", body);
152
153 Assertions.assertNotNull(a.declaringBlock());
154 Assertions.assertNotNull(result.declaringBlock());
155 Assertions.assertNotNull(result.op().ancestorBlock());
156 Assertions.assertNotNull(result.op().ancestorBody());
157 Assertions.assertNotNull(a.uses());
158 }
159
160 @Test
161 public void testPartiallyConstructedHeaderAccess() {
162 var body = Body.Builder.of(null, functionType(INT, INT, INT));
163 var block = body.entryBlock();
164 var anotherBlock = block.block(INT, INT);
165
166 var a = block.parameters().get(0);
167 var b = block.parameters().get(1);
168 Block.Reference successor = anotherBlock.successor(a, b);
169 // Access to target block before constructed
170 Assertions.assertThrows(IllegalStateException.class, successor::targetBlock);
171 block.op(branch(anotherBlock.successor(a, b)));
172
173 a = anotherBlock.parameters().get(0);
174 b = anotherBlock.parameters().get(1);
175 var result = anotherBlock.op(JavaOp.add(a, b));
176 anotherBlock.op(return_(result));
177
178 var f = func("f", body);
179
180 Assertions.assertNotNull(successor.targetBlock());
181 }
182
183 @Test
184 public void testValueUseFromOtherModel() {
185 var abody = Body.Builder.of(null, functionType(INT, INT, INT));
186 var ablock = abody.entryBlock();
187 var aa = ablock.parameters().get(0);
188 var ab = ablock.parameters().get(1);
189
190 var bbody = Body.Builder.of(null, abody.bodyType());
191 var bblock = bbody.entryBlock();
192
193 // Operation uses values from another model
194 var addOp = JavaOp.add(aa, ab);
195 Assertions.assertThrows(IllegalStateException.class, () -> bblock.op(addOp));
196 }
197
198 @Test
199 public void testHeaderFromOtherBody() {
200 var abody = Body.Builder.of(null, FUNCTION_TYPE_VOID);
201 var ablock = abody.entryBlock().block();
202
203 var bbody = Body.Builder.of(null, FUNCTION_TYPE_VOID);
204 var bblock = bbody.entryBlock();
205
206 // Operation uses header with target block from another model
207 var brOp = branch(ablock.successor());
208 Assertions.assertThrows(IllegalStateException.class, () -> bblock.op(brOp));
209 }
210
211 @Test
212 public void testHeaderFromEntryBlock() {
213 var body = Body.Builder.of(null, FUNCTION_TYPE_VOID);
214 var block = body.entryBlock();
215 Assertions.assertThrows(IllegalStateException.class, block::successor);
216 }
217
218 @Test
219 public void testBuiltBodyBuilder() {
220 var body = Body.Builder.of(null, FUNCTION_TYPE_VOID);
221 var block = body.entryBlock();
222 block.op(return_());
223 func("f", body);
224
225 // Body is built
226 Assertions.assertThrows(IllegalStateException.class, () -> func("f", body));
227 }
228
229 @Test
230 public void testBodyBuilderWithBuiltAncestor() {
231 var body = Body.Builder.of(null, FUNCTION_TYPE_VOID);
232 var block = body.entryBlock();
233 block.op(return_());
234 func("f", body);
235
236 // ancestor body is built
237 Assertions.assertThrows(IllegalStateException.class, () -> Body.Builder.of(body, FUNCTION_TYPE_VOID));
238 }
239
240 @Test
241 public void testBodyBuilderWithUnbuiltChildren() {
242 var body = Body.Builder.of(null, FUNCTION_TYPE_VOID);
243 var block = body.entryBlock();
244 block.op(return_());
245
246 Body.Builder.of(body, FUNCTION_TYPE_VOID);
247
248 // Great-grandchild body is not built
249 Assertions.assertThrows(IllegalStateException.class, () -> func("f", body));
250 }
251
252 @Test
253 public void testMistmatchedBody() {
254 var body1 = Body.Builder.of(null, FUNCTION_TYPE_VOID);
255 var block1 = body1.entryBlock();
256
257 var anotherBody = Body.Builder.of(null, FUNCTION_TYPE_VOID);
258
259 var body2 = Body.Builder.of(anotherBody, FUNCTION_TYPE_VOID);
260 var block2 = body2.entryBlock();
261 block2.op(return_());
262 var lambdaOp = JavaOp.lambda(type(Runnable.class), body2);
263
264 // Op's grandparent body is not parent body of block1
265 Assertions.assertThrows(IllegalStateException.class, () -> block1.op(lambdaOp));
266 }
267
268 @Test
269 public void testAppendAfterTerminatingOperation() {
270 var body = Body.Builder.of(null, FUNCTION_TYPE_VOID);
271 var block = body.entryBlock();
272 block.op(return_());
273
274 // Append operation after terminating operation
275 Assertions.assertThrows(IllegalStateException.class, () -> block.op(return_()));
276 }
277
278 @Test
279 public void testNoTerminatingOperation() {
280 var body = Body.Builder.of(null, FUNCTION_TYPE_VOID);
281 var block = body.entryBlock();
282 block.op(constant(INT, 0));
283
284 // No terminating operation
285 Assertions.assertThrows(IllegalStateException.class, () -> func("f", body));
286 }
287
288 @Test
289 public void testUnreferencedBlocksRemoved() {
290 var body = Body.Builder.of(null, FUNCTION_TYPE_VOID);
291 var block = body.entryBlock();
292 block.op(return_());
293
294 // Create empty blocks
295 block.block();
296 block.block();
297 block.block();
298
299 FuncOp f = func("f", body);
300 Assertions.assertEquals(1, f.body().blocks().size());
301 }
302
303 @Test
304 public void testEmptyEntryBlock() {
305 var body = Body.Builder.of(null, FUNCTION_TYPE_VOID);
306 var block = body.entryBlock();
307
308 Assertions.assertThrows(IllegalStateException.class, () -> func("f", body));
309 }
310
311 @Test
312 public void testNonEmptyEntryBlockNoTerminatingOp() {
313 var body = Body.Builder.of(null, FUNCTION_TYPE_VOID);
314 var block = body.entryBlock();
315 // No terminating op
316 block.op(constant(INT, 0));
317
318 Assertions.assertThrows(IllegalStateException.class, () -> func("f", body));
319 }
320
321 @Test
322 public void testEmptyBlockWithPredecessor() {
323 var body = Body.Builder.of(null, FUNCTION_TYPE_VOID);
324 var entryBlock = body.entryBlock();
325 // Create empty block
326 var block = entryBlock.block();
327 // Branch to empty block
328 entryBlock.op(branch(block.successor()));
329
330 Assertions.assertThrows(IllegalStateException.class, () -> func("f", body));
331 }
332
333 @Test
334 public void testNonEmptyBlockNoTerminatingOp() {
335 var body = Body.Builder.of(null, FUNCTION_TYPE_VOID);
336 var entryBlock = body.entryBlock();
337 // Create empty block
338 var block = entryBlock.block();
339 // Branch to empty block
340 entryBlock.op(branch(block.successor()));
341 // No terminating op
342 block.op(constant(INT, 0));
343
344 Assertions.assertThrows(IllegalStateException.class, () -> func("f", body));
345 }
346 }