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