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