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 }