lib/choir/src/dialects/arith/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const alloc_arena = @import("alloc_arena");
3 const ir = @import("../../core/root.zig");
4 const rewrite = ir.rewrite;
5 const arith = @import("root.zig");
6
7 const ArithDialect = arith.ArithDialect;
8 const CmpPredicate = arith.CmpPredicate;
9 const ScalarClass = arith.ScalarClass;
10 const ScalarKind = arith.ScalarKind;
11 const ScalarSet = arith.ScalarSet;
12 const type_names = arith.type_names;
13 const scalarBitWidth = arith.scalarBitWidth;
14 const scalarDescriptor = arith.scalarDescriptor;
15 const scalarKindFromTypeName = arith.scalarKindFromTypeName;
16 const scalarKindFromSuffix = arith.scalarKindFromSuffix;
17 const scalarKindIsInteger = arith.scalarKindIsInteger;
18 const scalarKindIsSignedInteger = arith.scalarKindIsSignedInteger;
19 const scalarKindIsUnsignedInteger = arith.scalarKindIsUnsignedInteger;
20 const scalarKindIsFloat = arith.scalarKindIsFloat;
21 const vectorTypeName = arith.vectorTypeName;
22 const vectorTypeNameForElement = arith.vectorTypeNameForElement;
23 const parseVectorTypeName = arith.parseVectorTypeName;
24
25 test {
26 std.testing.refAllDecls(arith);
27 }
28
29 test "ArithDialect.ConstantOp creates integer constant" {
30 const testing = std.testing;
31 var arena = alloc_arena.Arena.init(std.testing.allocator);
32 defer arena.deinit();
33 const allocator = arena.allocator();
34
35 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
36 defer ctx.deinit(allocator);
37
38 const loc = ir.Location.getUnknown();
39 const i32_type = try ArithDialect.getI32Type(&ctx);
40 var const_op = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 42);
41
42 try testing.expectEqualStrings("arith.constant", const_op.op.name.name);
43 try testing.expectEqual(@as(i64, 42), const_op.getIntValue().?);
44 }
45
46 test "ArithDialect registers unsigned u32 scalar and vector types" {
47 const testing = std.testing;
48 var arena = alloc_arena.Arena.init(std.testing.allocator);
49 defer arena.deinit();
50 const allocator = arena.allocator();
51
52 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
53 defer ctx.deinit(allocator);
54
55 const u32_type = try ArithDialect.getU32Type(&ctx);
56 const vec4_type = (try ArithDialect.getVecType(&ctx, 4, type_names.uint32)).?;
57
58 try testing.expectEqual(ScalarKind.u32, scalarKindFromTypeName(type_names.uint32).?);
59 try testing.expectEqual(ScalarKind.u32, scalarKindFromSuffix("u32").?);
60 try testing.expect(scalarKindIsInteger(.u32));
61 try testing.expect(!scalarKindIsFloat(.u32));
62 try testing.expectEqualStrings("arith.u32", u32_type.getDialectTypeName().?);
63 try testing.expectEqualStrings("arith.vec4xu32", vec4_type.getDialectTypeName().?);
64 try testing.expectEqualStrings(type_names.vec4xu32, vectorTypeName(4, .u32).?);
65 try testing.expectEqualStrings(type_names.vec4xu32, vectorTypeNameForElement(4, type_names.uint32).?);
66
67 const parsed = parseVectorTypeName(type_names.vec4xu32).?;
68 try testing.expectEqual(@as(u32, 4), parsed.width);
69 try testing.expectEqualStrings(type_names.uint32, parsed.elem_type_name);
70 }
71
72 test "ArithDialect registers unsigned u64 scalar and vector types" {
73 const testing = std.testing;
74 var arena = alloc_arena.Arena.init(std.testing.allocator);
75 defer arena.deinit();
76 const allocator = arena.allocator();
77
78 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
79 defer ctx.deinit(allocator);
80
81 const u64_type = try ArithDialect.getU64Type(&ctx);
82 const vec4_type = (try ArithDialect.getVecType(&ctx, 4, type_names.uint64)).?;
83
84 try testing.expectEqual(ScalarKind.u64, scalarKindFromTypeName(type_names.uint64).?);
85 try testing.expectEqual(ScalarKind.u64, scalarKindFromSuffix("u64").?);
86 try testing.expect(scalarKindIsInteger(.u64));
87 try testing.expect(scalarKindIsUnsignedInteger(.u64));
88 try testing.expect(!scalarKindIsFloat(.u64));
89 try testing.expectEqualStrings("arith.u64", u64_type.getDialectTypeName().?);
90 try testing.expectEqualStrings("arith.vec4xu64", vec4_type.getDialectTypeName().?);
91 try testing.expectEqualStrings(type_names.vec4xu64, vectorTypeName(4, .u64).?);
92 try testing.expectEqualStrings(type_names.vec4xu64, vectorTypeNameForElement(4, type_names.uint64).?);
93
94 const parsed = parseVectorTypeName(type_names.vec4xu64).?;
95 try testing.expectEqual(@as(u32, 4), parsed.width);
96 try testing.expectEqualStrings(type_names.uint64, parsed.elem_type_name);
97 }
98
99 test "ArithDialect registers bf16 scalar and vector types" {
100 const testing = std.testing;
101 var arena = alloc_arena.Arena.init(std.testing.allocator);
102 defer arena.deinit();
103 const allocator = arena.allocator();
104
105 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
106 defer ctx.deinit(allocator);
107
108 const bf16_type = try ArithDialect.getBf16Type(&ctx);
109 const vec4_type = (try ArithDialect.getVecType(&ctx, 4, type_names.bfloat16)).?;
110
111 try testing.expectEqual(ScalarKind.bf16, scalarKindFromTypeName(type_names.bfloat16).?);
112 try testing.expectEqual(ScalarKind.bf16, scalarKindFromSuffix("bf16").?);
113 try testing.expect(scalarKindIsFloat(.bf16));
114 try testing.expectEqualStrings("arith.bf16", bf16_type.getDialectTypeName().?);
115 try testing.expectEqualStrings("arith.vec4xbf16", vec4_type.getDialectTypeName().?);
116 try testing.expectEqualStrings(type_names.vec4xbf16, vectorTypeName(4, .bf16).?);
117 try testing.expectEqualStrings(type_names.vec4xbf16, vectorTypeNameForElement(4, type_names.bfloat16).?);
118
119 const parsed = parseVectorTypeName(type_names.vec4xbf16).?;
120 try testing.expectEqual(@as(u32, 4), parsed.width);
121 try testing.expectEqualStrings(type_names.bfloat16, parsed.elem_type_name);
122 }
123
124 test "ArithDialect.AddOp creates addition" {
125 const testing = std.testing;
126 var arena = alloc_arena.Arena.init(std.testing.allocator);
127 defer arena.deinit();
128 const allocator = arena.allocator();
129
130 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
131 defer ctx.deinit(allocator);
132
133 const loc = ir.Location.getUnknown();
134 const i32_type = try ArithDialect.getI32Type(&ctx);
135
136 var c1 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 10);
137 var c2 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 20);
138 var add = try ArithDialect.AddOp.create(&ctx, loc, c1.getResult(), c2.getResult());
139
140 try testing.expectEqualStrings("arith.add", add.op.name.name);
141 try testing.expect(add.getLhs() == c1.getResult());
142 try testing.expect(add.getRhs() == c2.getResult());
143 }
144
145 test "ArithDialect.CmpOp creates comparison" {
146 const testing = std.testing;
147 var arena = alloc_arena.Arena.init(std.testing.allocator);
148 defer arena.deinit();
149 const allocator = arena.allocator();
150
151 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
152 defer ctx.deinit(allocator);
153
154 const loc = ir.Location.getUnknown();
155 const i32_type = try ArithDialect.getI32Type(&ctx);
156
157 var c1 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 10);
158 var c2 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 20);
159 var cmp = try ArithDialect.CmpOp.create(&ctx, loc, .lt, c1.getResult(), c2.getResult());
160
161 try testing.expectEqualStrings("arith.cmp", cmp.op.name.name);
162 try testing.expectEqual(CmpPredicate.lt, cmp.getPredicate().?);
163 }
164
165 test "ArithDialect.FmaOp creates fused multiply-add" {
166 const testing = std.testing;
167 var arena = alloc_arena.Arena.init(std.testing.allocator);
168 defer arena.deinit();
169 const allocator = arena.allocator();
170
171 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
172 defer ctx.deinit(allocator);
173
174 const loc = ir.Location.getUnknown();
175 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
176
177 var a = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 2.0);
178 var b = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 3.0);
179 var c = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 4.0);
180 var fma = try ArithDialect.FmaOp.create(&ctx, loc, a.getResult(), b.getResult(), c.getResult());
181
182 try testing.expectEqualStrings("arith.fma", fma.op.name.name);
183 try testing.expect(fma.getA() == a.getResult());
184 try testing.expect(fma.getB() == b.getResult());
185 try testing.expect(fma.getC() == c.getResult());
186 }
187
188 test "ArithDialect.SqrtOp creates square root" {
189 const testing = std.testing;
190 var arena = alloc_arena.Arena.init(std.testing.allocator);
191 defer arena.deinit();
192 const allocator = arena.allocator();
193
194 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
195 defer ctx.deinit(allocator);
196
197 const loc = ir.Location.getUnknown();
198 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
199
200 var input = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 4.0);
201 var sqrt = try ArithDialect.SqrtOp.create(&ctx, loc, input.getResult());
202
203 try testing.expectEqualStrings("arith.sqrt", sqrt.op.name.name);
204 try testing.expect(sqrt.getInput() == input.getResult());
205 }
206
207 test "ArithDialect.SinOp creates sine" {
208 const testing = std.testing;
209 var arena = alloc_arena.Arena.init(std.testing.allocator);
210 defer arena.deinit();
211 const allocator = arena.allocator();
212
213 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
214 defer ctx.deinit(allocator);
215
216 const loc = ir.Location.getUnknown();
217 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
218
219 var input = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 0.0);
220 var sin = try ArithDialect.SinOp.create(&ctx, loc, input.getResult());
221
222 try testing.expectEqualStrings("arith.sin", sin.op.name.name);
223 try testing.expect(sin.getInput() == input.getResult());
224 }
225
226 test "ArithDialect.FloorOp, RoundOp, and TruncOp create rounding ops" {
227 const testing = std.testing;
228 var arena = alloc_arena.Arena.init(std.testing.allocator);
229 defer arena.deinit();
230 const allocator = arena.allocator();
231
232 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
233 defer ctx.deinit(allocator);
234
235 const loc = ir.Location.getUnknown();
236 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
237
238 var input = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, -1.5);
239 var floor = try ArithDialect.FloorOp.create(&ctx, loc, input.getResult());
240 var round = try ArithDialect.RoundOp.create(&ctx, loc, input.getResult());
241 var trunc = try ArithDialect.TruncOp.create(&ctx, loc, input.getResult());
242
243 try testing.expectEqualStrings("arith.floor", floor.op.name.name);
244 try testing.expect(floor.getInput() == input.getResult());
245 try testing.expectEqualStrings("arith.round", round.op.name.name);
246 try testing.expect(round.getInput() == input.getResult());
247 try testing.expectEqualStrings("arith.trunc", trunc.op.name.name);
248 try testing.expect(trunc.getInput() == input.getResult());
249 }
250
251 test "ArithDialect.Atan2Op creates arctangent with quadrant operands" {
252 const testing = std.testing;
253 var arena = alloc_arena.Arena.init(std.testing.allocator);
254 defer arena.deinit();
255 const allocator = arena.allocator();
256
257 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
258 defer ctx.deinit(allocator);
259
260 const loc = ir.Location.getUnknown();
261 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
262
263 var y = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 1.0);
264 var x = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, -1.0);
265 var atan2 = try ArithDialect.Atan2Op.create(&ctx, loc, y.getResult(), x.getResult());
266
267 try testing.expectEqualStrings("arith.atan2", atan2.op.name.name);
268 try testing.expect(atan2.getLhs() == y.getResult());
269 try testing.expect(atan2.getRhs() == x.getResult());
270 try testing.expect(atan2.getResult().type.eql(f32_type));
271 }
272
273 test "ArithDialect.TanhOp creates hyperbolic tangent" {
274 const testing = std.testing;
275 var arena = alloc_arena.Arena.init(std.testing.allocator);
276 defer arena.deinit();
277 const allocator = arena.allocator();
278
279 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
280 defer ctx.deinit(allocator);
281
282 const loc = ir.Location.getUnknown();
283 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
284
285 var input = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 0.5);
286 var tanh = try ArithDialect.TanhOp.create(&ctx, loc, input.getResult());
287
288 try testing.expectEqualStrings("arith.tanh", tanh.op.name.name);
289 try testing.expect(tanh.getInput() == input.getResult());
290 try testing.expect(tanh.getResult().type.eql(f32_type));
291 }
292
293 test "ArithDialect.MaxOp creates elementwise maximum" {
294 const testing = std.testing;
295 var arena = alloc_arena.Arena.init(std.testing.allocator);
296 defer arena.deinit();
297 const allocator = arena.allocator();
298
299 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
300 defer ctx.deinit(allocator);
301
302 const loc = ir.Location.getUnknown();
303 const i32_type = try ArithDialect.getI32Type(&ctx);
304
305 var lhs = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 3);
306 var rhs = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 7);
307 var max_op = try ArithDialect.MaxOp.create(&ctx, loc, lhs.getResult(), rhs.getResult());
308
309 try testing.expectEqualStrings("arith.max", max_op.op.name.name);
310 try testing.expect(max_op.getLhs() == lhs.getResult());
311 try testing.expect(max_op.getRhs() == rhs.getResult());
312 try testing.expect(max_op.getResult().type.eql(i32_type));
313 }
314
315 test "ArithDialect.MinOp creates elementwise minimum" {
316 const testing = std.testing;
317 var arena = alloc_arena.Arena.init(std.testing.allocator);
318 defer arena.deinit();
319 const allocator = arena.allocator();
320
321 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
322 defer ctx.deinit(allocator);
323
324 const loc = ir.Location.getUnknown();
325 const f64_type = try ArithDialect.getScalarType(&ctx, .f64);
326
327 var lhs = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f64_type, 1.5);
328 var rhs = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f64_type, 2.5);
329 var min_op = try ArithDialect.MinOp.create(&ctx, loc, lhs.getResult(), rhs.getResult());
330
331 try testing.expectEqualStrings("arith.min", min_op.op.name.name);
332 try testing.expect(min_op.getLhs() == lhs.getResult());
333 try testing.expect(min_op.getRhs() == rhs.getResult());
334 try testing.expect(min_op.getResult().type.eql(f64_type));
335 }
336
337 test "ArithDialect.AndOp creates bitwise AND" {
338 const testing = std.testing;
339 var arena = alloc_arena.Arena.init(std.testing.allocator);
340 defer arena.deinit();
341 const allocator = arena.allocator();
342
343 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
344 defer ctx.deinit(allocator);
345
346 const loc = ir.Location.getUnknown();
347 const i32_type = try ArithDialect.getI32Type(&ctx);
348
349 var c1 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 0xFF);
350 var c2 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 0x0F);
351 const and_op = try ArithDialect.AndOp.create(&ctx, loc, c1.getResult(), c2.getResult());
352
353 try testing.expectEqualStrings("arith.and", and_op.op.name.name);
354 }
355
356 test "ArithDialect.ShlOp creates shift left" {
357 const testing = std.testing;
358 var arena = alloc_arena.Arena.init(std.testing.allocator);
359 defer arena.deinit();
360 const allocator = arena.allocator();
361
362 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
363 defer ctx.deinit(allocator);
364
365 const loc = ir.Location.getUnknown();
366 const i32_type = try ArithDialect.getI32Type(&ctx);
367
368 var value = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 1);
369 var shift = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 4);
370 const shl = try ArithDialect.ShlOp.create(&ctx, loc, value.getResult(), shift.getResult());
371
372 try testing.expectEqualStrings("arith.shl", shl.op.name.name);
373 }
374
375 test "ArithDialect.UmulhiOp creates unsigned multiply high" {
376 const testing = std.testing;
377 var arena = alloc_arena.Arena.init(std.testing.allocator);
378 defer arena.deinit();
379 const allocator = arena.allocator();
380
381 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
382 defer ctx.deinit(allocator);
383
384 const loc = ir.Location.getUnknown();
385 const i32_type = try ArithDialect.getI32Type(&ctx);
386
387 var lhs = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 7);
388 var rhs = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 9);
389 const umulhi = try ArithDialect.UmulhiOp.create(&ctx, loc, lhs.getResult(), rhs.getResult());
390
391 try testing.expectEqualStrings("arith.umulhi", umulhi.op.name.name);
392 }
393
394 test "ArithDialect.BitcastOp creates bitcast" {
395 const testing = std.testing;
396 var arena = alloc_arena.Arena.init(std.testing.allocator);
397 defer arena.deinit();
398 const allocator = arena.allocator();
399
400 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
401 defer ctx.deinit(allocator);
402
403 const loc = ir.Location.getUnknown();
404 const i32_type = try ArithDialect.getI32Type(&ctx);
405 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
406
407 var input = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 0x40000000);
408 var bitcast = try ArithDialect.BitcastOp.create(&ctx, loc, input.getResult(), f32_type);
409
410 try testing.expectEqualStrings("arith.bitcast", bitcast.op.name.name);
411 try testing.expect(bitcast.getInput() == input.getResult());
412 }
413
414 test "ArithDialect.SplatOp creates vector splat" {
415 const testing = std.testing;
416 var arena = alloc_arena.Arena.init(std.testing.allocator);
417 defer arena.deinit();
418 const allocator = arena.allocator();
419
420 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
421 defer ctx.deinit(allocator);
422
423 const loc = ir.Location.getUnknown();
424 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
425 const vec4xf32_type = try ArithDialect.getVec4xF32Type(&ctx);
426
427 var scalar = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 1.0);
428 var splat = try ArithDialect.SplatOp.create(&ctx, loc, scalar.getResult(), vec4xf32_type);
429
430 try testing.expectEqualStrings("arith.splat", splat.op.name.name);
431 try testing.expect(splat.getInput() == scalar.getResult());
432 }
433
434 test "ArithDialect.ExtractOp creates vector extract" {
435 const testing = std.testing;
436 var arena = alloc_arena.Arena.init(std.testing.allocator);
437 defer arena.deinit();
438 const allocator = arena.allocator();
439
440 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
441 defer ctx.deinit(allocator);
442
443 const loc = ir.Location.getUnknown();
444 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
445 const vec4xf32_type = try ArithDialect.getVec4xF32Type(&ctx);
446
447 var vec_const = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32_type, 2.0);
448 var extract = try ArithDialect.ExtractOp.create(&ctx, loc, vec_const.getResult(), 2, f32_type);
449
450 try testing.expectEqualStrings("arith.extract", extract.op.name.name);
451 try testing.expectEqual(@as(i64, 2), extract.getIndex().?);
452 try testing.expect(extract.getVector() == vec_const.getResult());
453 }
454
455 test "ArithDialect.InsertOp creates vector insert" {
456 const testing = std.testing;
457 var arena = alloc_arena.Arena.init(std.testing.allocator);
458 defer arena.deinit();
459 const allocator = arena.allocator();
460
461 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
462 defer ctx.deinit(allocator);
463
464 const loc = ir.Location.getUnknown();
465 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
466 const vec4xf32_type = try ArithDialect.getVec4xF32Type(&ctx);
467
468 var vec_const = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32_type, 0.0);
469 var scalar = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 5.0);
470 var insert = try ArithDialect.InsertOp.create(&ctx, loc, vec_const.getResult(), scalar.getResult(), 1);
471
472 try testing.expectEqualStrings("arith.insert", insert.op.name.name);
473 try testing.expectEqual(@as(i64, 1), insert.getIndex().?);
474 try testing.expect(insert.getVector() == vec_const.getResult());
475 try testing.expect(insert.getScalar() == scalar.getResult());
476 }
477
478 test "ArithDialect.VecShuffleOp creates vector shuffle" {
479 const testing = std.testing;
480 var arena = alloc_arena.Arena.init(std.testing.allocator);
481 defer arena.deinit();
482 const allocator = arena.allocator();
483
484 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
485 defer ctx.deinit(allocator);
486
487 const loc = ir.Location.getUnknown();
488 const vec4xi32_type = try ArithDialect.getVec4xI32Type(&ctx);
489
490 var vec_const = try ArithDialect.VecConstantOp.createInt(&ctx, loc, vec4xi32_type, 1);
491 const indices = [_]i64{ 3, 1, 2, 0 };
492 var shuffle = try ArithDialect.VecShuffleOp.create(&ctx, loc, vec_const.getResult(), vec4xi32_type, indices[0..]);
493
494 try testing.expectEqualStrings("arith.vec_shuffle", shuffle.op.name.name);
495 try testing.expect(shuffle.getVector() == vec_const.getResult());
496
497 var buf: [4]i64 = undefined;
498 const parsed = try shuffle.getIndices(&buf);
499 try testing.expectEqual(@as(usize, indices.len), parsed.len);
500 for (parsed, indices) |got, expected| {
501 try testing.expectEqual(expected, got);
502 }
503 }
504
505 test "ArithDialect.AddOp creates vector addition" {
506 const testing = std.testing;
507 var arena = alloc_arena.Arena.init(std.testing.allocator);
508 defer arena.deinit();
509 const allocator = arena.allocator();
510
511 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
512 defer ctx.deinit(allocator);
513
514 const loc = ir.Location.getUnknown();
515 const vec4xf32_type = try ArithDialect.getVec4xF32Type(&ctx);
516
517 var v1 = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32_type, 1.0);
518 var v2 = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32_type, 2.0);
519 var add = try ArithDialect.AddOp.create(&ctx, loc, v1.getResult(), v2.getResult());
520
521 try testing.expectEqualStrings("arith.add", add.op.name.name);
522 try testing.expect(add.getLhs() == v1.getResult());
523 try testing.expect(add.getRhs() == v2.getResult());
524 }
525
526 test "ArithDialect.MulOp creates vector multiplication" {
527 const testing = std.testing;
528 var arena = alloc_arena.Arena.init(std.testing.allocator);
529 defer arena.deinit();
530 const allocator = arena.allocator();
531
532 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
533 defer ctx.deinit(allocator);
534
535 const loc = ir.Location.getUnknown();
536 const vec4xi32_type = try ArithDialect.getVec4xI32Type(&ctx);
537
538 var v1 = try ArithDialect.VecConstantOp.createInt(&ctx, loc, vec4xi32_type, 3);
539 var v2 = try ArithDialect.VecConstantOp.createInt(&ctx, loc, vec4xi32_type, 4);
540 const mul = try ArithDialect.MulOp.create(&ctx, loc, v1.getResult(), v2.getResult());
541
542 try testing.expectEqualStrings("arith.mul", mul.op.name.name);
543 }
544
545 test "ArithDialect.NegOp creates vector negation" {
546 const testing = std.testing;
547 var arena = alloc_arena.Arena.init(std.testing.allocator);
548 defer arena.deinit();
549 const allocator = arena.allocator();
550
551 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
552 defer ctx.deinit(allocator);
553
554 const loc = ir.Location.getUnknown();
555 const vec4xf32_type = try ArithDialect.getVec4xF32Type(&ctx);
556
557 var v = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32_type, 5.0);
558 var neg = try ArithDialect.NegOp.create(&ctx, loc, v.getResult());
559
560 try testing.expectEqualStrings("arith.neg", neg.op.name.name);
561 try testing.expect(neg.getInput() == v.getResult());
562 }
563
564 test "ArithDialect.VecConstantOp creates vector constant" {
565 const testing = std.testing;
566 var arena = alloc_arena.Arena.init(std.testing.allocator);
567 defer arena.deinit();
568 const allocator = arena.allocator();
569
570 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
571 defer ctx.deinit(allocator);
572 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
573
574 const loc = ir.Location.getUnknown();
575 const vec4xf32_type = try ArithDialect.getVec4xF32Type(&ctx);
576
577 var vec_const = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32_type, 3.14);
578
579 try testing.expectEqualStrings("arith.vec_constant", vec_const.op.name.name);
580 try testing.expectEqual(@as(usize, 0), vec_const.op.getRawDictionaryAttrs().len);
581 try testing.expect((try vec_const.op.getPropertiesAsAttr()).?.eql(vec_const.op.getAttr("value").?));
582 const val = vec_const.getFloatValue().?;
583 try testing.expect(@abs(val - 3.14) < 0.001);
584 }
585
586 test "ArithDialect.getVecType returns correct types" {
587 const testing = std.testing;
588 var arena = alloc_arena.Arena.init(std.testing.allocator);
589 defer arena.deinit();
590 const allocator = arena.allocator();
591
592 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
593 defer ctx.deinit(allocator);
594
595 const vec4xf32 = try ArithDialect.getVecType(&ctx, 4, type_names.float32);
596 try testing.expect(vec4xf32 != null);
597
598 const vec8xi32 = try ArithDialect.getVecType(&ctx, 8, type_names.int32);
599 try testing.expect(vec8xi32 != null);
600
601 const invalid_width = try ArithDialect.getVecType(&ctx, 3, type_names.float32);
602 try testing.expect(invalid_width == null);
603 }
604
605 test "arith scalar and vector type schema derives names and parse facts" {
606 const testing = std.testing;
607
608 try testing.expectEqual(ScalarKind.f32, scalarKindFromTypeName(type_names.float32).?);
609 try testing.expectEqual(ScalarKind.index, scalarKindFromSuffix("index").?);
610 try testing.expect(scalarKindIsInteger(.i64));
611 try testing.expect(scalarKindIsUnsignedInteger(.u64));
612 try testing.expect(!scalarKindIsInteger(.f32));
613 try testing.expect(scalarKindIsFloat(.bf16));
614 try testing.expect(scalarKindIsFloat(.f64));
615 try testing.expectEqualStrings(type_names.vec4xf32, vectorTypeName(4, .f32).?);
616 try testing.expectEqualStrings(type_names.vec8xindex, vectorTypeNameForElement(8, type_names.index).?);
617 try testing.expect(vectorTypeName(16, .f64) == null);
618
619 const parsed = parseVectorTypeName(type_names.vec8xi32) orelse return error.TestExpectedVectorType;
620 try testing.expectEqual(@as(u32, 8), parsed.width);
621 try testing.expectEqualStrings(type_names.int32, parsed.elem_type_name);
622 try testing.expect(parseVectorTypeName("arith.vec16xf64") == null);
623 try testing.expect(parseVectorTypeName("arith.vec0xi32") == null);
624 }
625
626 test "arith scalar descriptors exhaustively derive spelling class width and sets" {
627 const testing = std.testing;
628 var all: ScalarSet = .{};
629
630 inline for (std.meta.tags(ScalarKind)) |kind| {
631 const descriptor = scalarDescriptor(kind);
632 all.insert(kind);
633 try testing.expectEqual(kind, descriptor.kind);
634 try testing.expectEqualStrings(@tagName(kind), descriptor.suffix);
635 try testing.expect(std.mem.startsWith(u8, descriptor.name, "arith."));
636 try testing.expectEqualStrings(descriptor.suffix, descriptor.name["arith.".len..]);
637 try testing.expectEqual(descriptor.bit_width, scalarBitWidth(kind));
638 try testing.expect(descriptor.bit_width > 0 and descriptor.bit_width <= 64);
639 switch (descriptor.class) {
640 ScalarClass.boolean => {
641 try testing.expect(!scalarKindIsInteger(kind));
642 try testing.expect(!scalarKindIsFloat(kind));
643 },
644 .signed_integer, .index => try testing.expect(scalarKindIsSignedInteger(kind)),
645 .unsigned_integer => try testing.expect(scalarKindIsUnsignedInteger(kind)),
646 .float => try testing.expect(scalarKindIsFloat(kind)),
647 }
648 }
649
650 const integer = ScalarSet.init(&.{ .i8, .i16, .i32, .i64, .u8, .u16, .u32, .u64, .index });
651 const floating = ScalarSet.init(&.{ .f16, .bf16, .f32, .f64 });
652 try testing.expect(all.containsAll(integer));
653 try testing.expect(all.containsAll(floating));
654 try testing.expect(!integer.contains(.f32));
655 try testing.expect(!floating.contains(.bool));
656 }
657
658 test "arith dialect registers semantic and verifier traits for AddOp" {
659 const testing = std.testing;
660 var arena = alloc_arena.Arena.init(std.testing.allocator);
661 defer arena.deinit();
662 const allocator = arena.allocator();
663
664 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
665 defer ctx.deinit(allocator);
666
667 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
668
669 const info = ctx.lookupOperation("arith.add") orelse
670 return error.TestExpectedOperationInfo;
671 try testing.expect(info.getInterface(ir.interfaces.EffectOpInterface.id) != null);
672 try testing.expect(info.traits.is_commutative);
673 try testing.expect(info.shape.operands.allows(2));
674 try testing.expect(!info.shape.operands.allows(1));
675 try testing.expect(info.shape.results.allows(1));
676 try testing.expect(!info.shape.results.allows(0));
677 try testing.expect(info.shape.regions.allows(0));
678 try testing.expect(!info.shape.regions.allows(1));
679 try testing.expect(info.shape.successors.allows(0));
680 try testing.expect(!info.shape.successors.allows(1));
681 try testing.expect(info.hasTraitId(ir.traits.SameOperandsAndResultType.id));
682 try testing.expect(info.hasInterface(ir.interfaces.FoldOpInterface.id));
683 try expectCanonicalizationPatterns(&ctx);
684 _ = try ArithDialect.getBoolAttr(&ctx, false);
685 _ = try ArithDialect.getBoolAttr(&ctx, true);
686 ctx.activate();
687 try expectCanonicalizationPatterns(&ctx);
688 try exerciseBooleanSelectRewrite(&ctx, allocator);
689 }
690
691 fn expectCanonicalizationPatterns(ctx: *ir.Context) !void {
692 const hook = rewrite.DialectCanonicalizationInterface;
693 const vtable_opaque = ctx.getDialectInterface(ArithDialect.name, hook.id) orelse
694 return error.TestExpectedCanonicalizationInterface;
695 const patterns = hook.fromOpaque(vtable_opaque).patterns;
696 try std.testing.expectEqual(arith.canonicalization_patterns.len, patterns.len);
697 try std.testing.expectEqual(arith.canonicalization_patterns[0..].ptr, patterns.ptr);
698 try std.testing.expectEqualStrings(ArithDialect.SelectOp.operation_name, patterns[0].spec.root_op_name);
699 try std.testing.expectEqualStrings(ArithDialect.NotOp.operation_name, patterns[1].spec.root_op_name);
700 }
701
702 fn exerciseBooleanSelectRewrite(ctx: *ir.Context, allocator: std.mem.Allocator) !void {
703 const hook = rewrite.DialectCanonicalizationInterface;
704 const patterns = hook.fromOpaque(ctx.getDialectInterface(ArithDialect.name, hook.id).?).patterns;
705 var block = ir.Block.init(allocator);
706 defer block.deinit();
707 const boundary = ctx.operationCreationBoundary();
708 defer {
709 var operations = block.getOperations();
710 while (operations.next()) |op| op.dropAllReferences();
711 ctx.eraseOperationsCreatedSince(boundary);
712 }
713 const loc = ir.Location.getUnknown();
714 const bool_type = try ArithDialect.getScalarType(ctx, .bool);
715 const condition = try block.addArgument(bool_type, loc);
716 const false_value = try ArithDialect.ConstantOp.createBool(ctx, loc, false);
717 try block.addOperation(false_value.op);
718 const true_value = try ArithDialect.ConstantOp.createBool(ctx, loc, true);
719 try block.addOperation(true_value.op);
720 const select = try ArithDialect.SelectOp.create(
721 ctx,
722 loc,
723 condition,
724 false_value.getResult(),
725 true_value.getResult(),
726 );
727 try block.addOperation(select.op);
728 const user = try ArithDialect.NotOp.create(ctx, loc, select.getResult());
729 try block.addOperation(user.op);
730 var rewriter = rewrite.PatternRewriter.init(allocator, ctx);
731 defer rewriter.deinit();
732 try std.testing.expect(patterns[0].matches(select.op));
733 try std.testing.expect(rewrite.tryApplyRewritePattern(&patterns[0], select.op, &rewriter));
734 rewriter.finalize(user.op);
735 const result = user.op.getOperand(0).?;
736 const inverse: *ir.Operation = @ptrCast(@alignCast(result.getDefiningOp().?));
737 try std.testing.expectEqualStrings(ArithDialect.NotOp.operation_name, inverse.name.name);
738 try std.testing.expectEqual(condition, inverse.getOperand(0).?);
739 }
740
741 test "arith dialect registers single-entity type constraints as operation metadata" {
742 const testing = std.testing;
743 var arena = alloc_arena.Arena.init(std.testing.allocator);
744 defer arena.deinit();
745 const allocator = arena.allocator();
746
747 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
748 defer ctx.deinit(allocator);
749
750 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
751
752 const cmp_info = ctx.lookupOperation(ArithDialect.CmpOp.operation_name) orelse
753 return error.TestExpectedOperationInfo;
754 try testing.expectEqual(@as(usize, 1), cmp_info.getResultTypeConstraints().len);
755 try testing.expectEqual(@as(usize, 0), cmp_info.getResultTypeConstraints()[0].index);
756 try testing.expectEqualStrings(type_names.boolean, cmp_info.getResultTypeConstraints()[0].type_name);
757 try testing.expect(!cmp_info.getResultTypeConstraints()[0].allow_parameterized);
758 try testing.expect(cmp_info.hasTraitId(ir.traits.SameTypeOperands.id));
759
760 const select_info = ctx.lookupOperation(ArithDialect.SelectOp.operation_name) orelse
761 return error.TestExpectedOperationInfo;
762 try testing.expectEqual(@as(usize, 1), select_info.getOperandTypeConstraints().len);
763 try testing.expectEqual(@as(usize, 0), select_info.getOperandTypeConstraints()[0].index);
764 try testing.expectEqualStrings(type_names.boolean, select_info.getOperandTypeConstraints()[0].type_name);
765 try testing.expect(!select_info.getOperandTypeConstraints()[0].allow_parameterized);
766 }
767
768 test "arith.add with matching i32 operands passes SameOperandsAndResultType verification" {
769 var arena = alloc_arena.Arena.init(std.testing.allocator);
770 defer arena.deinit();
771 const allocator = arena.allocator();
772
773 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
774 defer ctx.deinit(allocator);
775 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
776
777 const loc = ir.Location.getUnknown();
778 const i32_type = try ArithDialect.getI32Type(&ctx);
779
780 var c1 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 10);
781 var c2 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 20);
782 const add = try ArithDialect.AddOp.create(&ctx, loc, c1.getResult(), c2.getResult());
783
784 try ir.verify.verifyOperation(add.op, .{ .recursive = false });
785 }
786
787 test "arith.add with mismatched i32/i64 operands fails SameOperandsAndResultType verification" {
788 const testing = std.testing;
789 var arena = alloc_arena.Arena.init(std.testing.allocator);
790 defer arena.deinit();
791 const allocator = arena.allocator();
792
793 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
794 defer ctx.deinit(allocator);
795 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
796
797 const loc = ir.Location.getUnknown();
798 const i32_type = try ArithDialect.getI32Type(&ctx);
799 const i64_type = try ArithDialect.getScalarType(&ctx, .i64);
800
801 var c1 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 10);
802 var c2 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i64_type, 20);
803 const add = try ArithDialect.AddOp.create(&ctx, loc, c1.getResult(), c2.getResult());
804
805 try testing.expectError(
806 ir.traits.TraitError.SameOperandsAndResultTypeMismatch,
807 ir.verify.verifyOperation(add.op, .{ .recursive = false }),
808 );
809 }
810
811 test "arith.neg over matching operand passes verification" {
812 var arena = alloc_arena.Arena.init(std.testing.allocator);
813 defer arena.deinit();
814 const allocator = arena.allocator();
815
816 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
817 defer ctx.deinit(allocator);
818 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
819
820 const loc = ir.Location.getUnknown();
821 const i32_type = try ArithDialect.getI32Type(&ctx);
822
823 var c = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 5);
824 const neg = try ArithDialect.NegOp.create(&ctx, loc, c.getResult());
825 try ir.verify.verifyOperation(neg.op, .{ .recursive = false });
826 }
827
828 test "arith.select with mismatched true/false operand types fails verification" {
829 const testing = std.testing;
830 var arena = alloc_arena.Arena.init(std.testing.allocator);
831 defer arena.deinit();
832 const allocator = arena.allocator();
833
834 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
835 defer ctx.deinit(allocator);
836 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
837
838 const loc = ir.Location.getUnknown();
839 const bool_type = try ArithDialect.getScalarType(&ctx, .bool);
840 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
841 const f64_type = try ArithDialect.getF64Type(&ctx);
842
843 var cond = try ArithDialect.ConstantOp.createInt(&ctx, loc, bool_type, 1);
844 var true_val = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 1.0);
845 var false_val = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f64_type, 2.0);
846 const sel = try ArithDialect.SelectOp.create(&ctx, loc, cond.getResult(), true_val.getResult(), false_val.getResult());
847
848 try testing.expectError(
849 ir.traits.TraitError.TypesMatchWithMismatch,
850 ir.verify.verifyOperation(sel.op, .{ .recursive = false }),
851 );
852 }
853
854 test "arith.select with mismatched result vs. true operand type fails verification" {
855 const testing = std.testing;
856 var arena = alloc_arena.Arena.init(std.testing.allocator);
857 defer arena.deinit();
858 const allocator = arena.allocator();
859
860 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
861 defer ctx.deinit(allocator);
862 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
863
864 const loc = ir.Location.getUnknown();
865 const bool_type = try ArithDialect.getScalarType(&ctx, .bool);
866 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
867 const f64_type = try ArithDialect.getF64Type(&ctx);
868
869 var cond = try ArithDialect.ConstantOp.createInt(&ctx, loc, bool_type, 0);
870 var true_val = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 3.0);
871 var false_val = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 4.0);
872
873 var state = ir.Operation.State.init(ArithDialect.SelectOp.operation_name, loc);
874 state.addOperands(&.{ cond.getResult(), true_val.getResult(), false_val.getResult() });
875 state.addTypes(&.{f64_type});
876 const sel_op = try ctx.createOperation(state);
877
878 try testing.expectError(
879 ir.traits.TraitError.TypesMatchWithMismatch,
880 ir.verify.verifyOperation(sel_op, .{ .recursive = false }),
881 );
882 }
883
884 test "arith.select with non-bool cond operand fails verification" {
885 const testing = std.testing;
886 var arena = alloc_arena.Arena.init(std.testing.allocator);
887 defer arena.deinit();
888 const allocator = arena.allocator();
889
890 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
891 defer ctx.deinit(allocator);
892 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
893
894 const loc = ir.Location.getUnknown();
895 const i32_type = try ArithDialect.getI32Type(&ctx);
896 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
897
898 var cond = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 1);
899 var true_val = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 1.0);
900 var false_val = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 2.0);
901 const sel = try ArithDialect.SelectOp.create(&ctx, loc, cond.getResult(), true_val.getResult(), false_val.getResult());
902
903 try testing.expectError(
904 ir.VerifyError.OperandTypeConstraintMismatch,
905 ir.verify.verifyOperation(sel.op, .{ .recursive = false }),
906 );
907 }
908
909 test "arith.select with bool cond operand passes verification" {
910 var arena = alloc_arena.Arena.init(std.testing.allocator);
911 defer arena.deinit();
912 const allocator = arena.allocator();
913
914 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
915 defer ctx.deinit(allocator);
916 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
917
918 const loc = ir.Location.getUnknown();
919 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
920
921 var cond = try ArithDialect.ConstantOp.createBool(&ctx, loc, true);
922 var true_val = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 1.0);
923 var false_val = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 2.0);
924 const sel = try ArithDialect.SelectOp.create(&ctx, loc, cond.getResult(), true_val.getResult(), false_val.getResult());
925
926 try ir.verify.verifyOperation(sel.op, .{ .recursive = false });
927 }
928
929 test "arith.fma with mismatched 3rd operand fails verification" {
930 const testing = std.testing;
931 var arena = alloc_arena.Arena.init(std.testing.allocator);
932 defer arena.deinit();
933 const allocator = arena.allocator();
934
935 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
936 defer ctx.deinit(allocator);
937 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
938
939 const loc = ir.Location.getUnknown();
940 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
941 const f64_type = try ArithDialect.getF64Type(&ctx);
942
943 var ca = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 1.0);
944 var cb = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 2.0);
945 var cc = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f64_type, 3.0);
946 const fma = try ArithDialect.FmaOp.create(&ctx, loc, ca.getResult(), cb.getResult(), cc.getResult());
947
948 try testing.expectError(
949 ir.traits.TraitError.SameOperandsAndResultTypeMismatch,
950 ir.verify.verifyOperation(fma.op, .{ .recursive = false }),
951 );
952 }
953
954 test "arith.shl with mismatched count width fails verification" {
955 const testing = std.testing;
956 var arena = alloc_arena.Arena.init(std.testing.allocator);
957 defer arena.deinit();
958 const allocator = arena.allocator();
959
960 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
961 defer ctx.deinit(allocator);
962 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
963
964 const loc = ir.Location.getUnknown();
965 const i32_type = try ArithDialect.getI32Type(&ctx);
966 const i64_type = try ArithDialect.getScalarType(&ctx, .i64);
967
968 var v = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 5);
969 var c64 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i64_type, 2);
970 const shl = try ArithDialect.ShlOp.create(&ctx, loc, v.getResult(), c64.getResult());
971
972 try testing.expectError(
973 ir.traits.TraitError.SameOperandsAndResultTypeMismatch,
974 ir.verify.verifyOperation(shl.op, .{ .recursive = false }),
975 );
976 }
977
978 test "arith.constant verifies cleanly without SameOperandsAndResultType (zero operands)" {
979 var arena = alloc_arena.Arena.init(std.testing.allocator);
980 defer arena.deinit();
981 const allocator = arena.allocator();
982
983 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
984 defer ctx.deinit(allocator);
985 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
986
987 const loc = ir.Location.getUnknown();
988 const i32_type = try ArithDialect.getI32Type(&ctx);
989 const c = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 42);
990
991 try ir.verify.verifyOperation(c.op, .{ .recursive = false });
992 }
993
994 test "arith.constant stores value as operation properties" {
995 const testing = std.testing;
996 var arena = alloc_arena.Arena.init(testing.allocator);
997 defer arena.deinit();
998 const allocator = arena.allocator();
999
1000 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1001 defer ctx.deinit(allocator);
1002 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1003
1004 const loc = ir.Location.getUnknown();
1005 const i32_type = try ArithDialect.getI32Type(&ctx);
1006 const c = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 42);
1007
1008 try testing.expectEqual(@as(usize, 0), c.op.getRawDictionaryAttrs().len);
1009 try testing.expectEqual(@as(?i64, 42), c.getIntValue());
1010 try testing.expect((try c.op.getPropertiesAsAttr()).?.eql(c.op.getAttr("value").?));
1011
1012 try c.op.setAttr("debug.note", try ctx.getI64Attr(7));
1013 try testing.expectEqual(@as(usize, 1), c.op.getRawDictionaryAttrs().len);
1014 try testing.expectEqualStrings("debug.note", c.op.getRawDictionaryAttrs()[0].name);
1015
1016 try testing.expectEqual(@as(usize, 2), c.op.getNumAttrs());
1017 var attrs = c.op.getAttrs();
1018 try testing.expectEqualStrings("debug.note", attrs.next().?.name);
1019 const value = attrs.next().?;
1020 try testing.expectEqualStrings("value", value.name);
1021 try testing.expect(value.value.eql(c.op.getAttr("value").?));
1022 try testing.expect(attrs.next() == null);
1023 }
1024
1025 test "arith.cmp verifies cleanly without SameOperandsAndResultType (bool result)" {
1026 var arena = alloc_arena.Arena.init(std.testing.allocator);
1027 defer arena.deinit();
1028 const allocator = arena.allocator();
1029
1030 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1031 defer ctx.deinit(allocator);
1032 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1033
1034 const loc = ir.Location.getUnknown();
1035 const i32_type = try ArithDialect.getI32Type(&ctx);
1036 var c1 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 1);
1037 var c2 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 2);
1038 const cmp = try ArithDialect.CmpOp.create(&ctx, loc, .eq, c1.getResult(), c2.getResult());
1039 try ir.verify.verifyOperation(cmp.op, .{ .recursive = false });
1040 }
1041
1042 test "arith.cmp with matching i32 operands passes SameTypeOperands verification" {
1043 var arena = alloc_arena.Arena.init(std.testing.allocator);
1044 defer arena.deinit();
1045 const allocator = arena.allocator();
1046
1047 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1048 defer ctx.deinit(allocator);
1049 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1050
1051 const loc = ir.Location.getUnknown();
1052 const i32_type = try ArithDialect.getI32Type(&ctx);
1053 var c1 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 1);
1054 var c2 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 2);
1055 const cmp = try ArithDialect.CmpOp.create(&ctx, loc, .eq, c1.getResult(), c2.getResult());
1056 try ir.verify.verifyOperation(cmp.op, .{ .recursive = false });
1057 }
1058
1059 test "arith.cmp with mismatched i32 / i64 operands fails SameTypeOperands verification" {
1060 const testing = std.testing;
1061 var arena = alloc_arena.Arena.init(std.testing.allocator);
1062 defer arena.deinit();
1063 const allocator = arena.allocator();
1064
1065 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1066 defer ctx.deinit(allocator);
1067 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1068
1069 const loc = ir.Location.getUnknown();
1070 const i32_type = try ArithDialect.getI32Type(&ctx);
1071 const i64_type = try ArithDialect.getScalarType(&ctx, .i64);
1072 var c1 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 1);
1073 var c2 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i64_type, 2);
1074 const cmp = try ArithDialect.CmpOp.create(&ctx, loc, .eq, c1.getResult(), c2.getResult());
1075
1076 try testing.expectError(
1077 ir.traits.TraitError.SameTypeOperandsMismatch,
1078 ir.verify.verifyOperation(cmp.op, .{ .recursive = false }),
1079 );
1080 }
1081
1082 test "arith.cmp with non-bool result fails type constraint verification" {
1083 const testing = std.testing;
1084 var arena = alloc_arena.Arena.init(std.testing.allocator);
1085 defer arena.deinit();
1086 const allocator = arena.allocator();
1087
1088 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1089 defer ctx.deinit(allocator);
1090 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1091
1092 const loc = ir.Location.getUnknown();
1093 const i32_type = try ArithDialect.getI32Type(&ctx);
1094 var c1 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 1);
1095 var c2 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 2);
1096
1097 var state = ir.Operation.State.init(ArithDialect.CmpOp.operation_name, loc);
1098 state.addOperands(&.{ c1.getResult(), c2.getResult() });
1099 state.addTypes(&.{i32_type});
1100 const cmp_op = try ctx.createOperation(state);
1101 try cmp_op.setAttr("predicate", try ctx.getDialectAttr(ArithDialect.name ++ ".predicate", "eq"));
1102
1103 try testing.expectError(
1104 ir.VerifyError.ResultTypeConstraintMismatch,
1105 ir.verify.verifyOperation(cmp_op, .{ .recursive = false }),
1106 );
1107 }
1108
1109 test "arith.cmp with bool result passes type constraint verification" {
1110 var arena = alloc_arena.Arena.init(std.testing.allocator);
1111 defer arena.deinit();
1112 const allocator = arena.allocator();
1113
1114 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1115 defer ctx.deinit(allocator);
1116 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1117
1118 const loc = ir.Location.getUnknown();
1119 const i32_type = try ArithDialect.getI32Type(&ctx);
1120 var c1 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 1);
1121 var c2 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 2);
1122
1123 const cmp = try ArithDialect.CmpOp.create(&ctx, loc, .eq, c1.getResult(), c2.getResult());
1124 try ir.verify.verifyOperation(cmp.op, .{ .recursive = false });
1125 }
1126
1127 test "arith.select with keyed-variant cond type fails type constraint verification" {
1128 const testing = std.testing;
1129 var arena = alloc_arena.Arena.init(std.testing.allocator);
1130 defer arena.deinit();
1131 const allocator = arena.allocator();
1132
1133 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1134 defer ctx.deinit(allocator);
1135 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1136
1137 const loc = ir.Location.getUnknown();
1138 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
1139 const keyed_bool = try ctx.getDialectTypeFromNameWithKey("arith.bool", "custom-key");
1140
1141 var c_cond_state = ir.Operation.State.init(ArithDialect.ConstantOp.operation_name, loc);
1142 c_cond_state.addTypes(&.{keyed_bool});
1143 const c_cond = try ctx.createOperation(c_cond_state);
1144
1145 var t_val = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 1.0);
1146 var f_val = try ArithDialect.ConstantOp.createFloat(&ctx, loc, f32_type, 2.0);
1147 const sel = try ArithDialect.SelectOp.create(&ctx, loc, c_cond.getResult(0).?, t_val.getResult(), f_val.getResult());
1148
1149 try testing.expectError(
1150 ir.VerifyError.OperandTypeConstraintMismatch,
1151 ir.verify.verifyOperation(sel.op, .{ .recursive = false }),
1152 );
1153 }
1154
1155 test "arith.cmp with keyed-variant result type fails type constraint verification" {
1156 const testing = std.testing;
1157 var arena = alloc_arena.Arena.init(std.testing.allocator);
1158 defer arena.deinit();
1159 const allocator = arena.allocator();
1160
1161 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1162 defer ctx.deinit(allocator);
1163 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1164
1165 const loc = ir.Location.getUnknown();
1166 const i32_type = try ArithDialect.getI32Type(&ctx);
1167 const keyed_bool = try ctx.getDialectTypeFromNameWithKey("arith.bool", "custom-key");
1168 var c1 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 1);
1169 var c2 = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 2);
1170
1171 var state = ir.Operation.State.init(ArithDialect.CmpOp.operation_name, loc);
1172 state.addOperands(&.{ c1.getResult(), c2.getResult() });
1173 state.addTypes(&.{keyed_bool});
1174 const cmp_op = try ctx.createOperation(state);
1175 try cmp_op.setAttr("predicate", try ctx.getDialectAttr(ArithDialect.name ++ ".predicate", "eq"));
1176
1177 try testing.expectError(
1178 ir.VerifyError.ResultTypeConstraintMismatch,
1179 ir.verify.verifyOperation(cmp_op, .{ .recursive = false }),
1180 );
1181 }
1182
1183 test "arith.vec_cmp with matching vec4xf32 operands passes SameTypeOperands verification" {
1184 var arena = alloc_arena.Arena.init(std.testing.allocator);
1185 defer arena.deinit();
1186 const allocator = arena.allocator();
1187
1188 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1189 defer ctx.deinit(allocator);
1190 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1191
1192 const loc = ir.Location.getUnknown();
1193 const vec4xf32 = try ArithDialect.getVec4xF32Type(&ctx);
1194
1195 var c1 = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32, 1.0);
1196 var c2 = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32, 2.0);
1197 const cmp = try ArithDialect.VecCmpOp.create(&ctx, loc, .eq, c1.getResult(), c2.getResult(), vec4xf32);
1198 try ir.verify.verifyOperation(cmp.op, .{ .recursive = false });
1199 }
1200
1201 test "arith.vec_cmp with mismatched vector types fails SameTypeOperands verification" {
1202 const testing = std.testing;
1203 var arena = alloc_arena.Arena.init(std.testing.allocator);
1204 defer arena.deinit();
1205 const allocator = arena.allocator();
1206
1207 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1208 defer ctx.deinit(allocator);
1209 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1210
1211 const loc = ir.Location.getUnknown();
1212 const vec4xf32 = try ArithDialect.getVec4xF32Type(&ctx);
1213 const vec8xf32 = (try ArithDialect.getVecType(&ctx, 8, type_names.float32)).?;
1214
1215 var c1 = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32, 1.0);
1216 var c2 = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec8xf32, 2.0);
1217 const cmp = try ArithDialect.VecCmpOp.create(&ctx, loc, .eq, c1.getResult(), c2.getResult(), vec4xf32);
1218
1219 try testing.expectError(
1220 ir.traits.TraitError.SameTypeOperandsMismatch,
1221 ir.verify.verifyOperation(cmp.op, .{ .recursive = false }),
1222 );
1223 }
1224
1225 test "arith.add over matching vec4xf32 operands passes verification" {
1226 var arena = alloc_arena.Arena.init(std.testing.allocator);
1227 defer arena.deinit();
1228 const allocator = arena.allocator();
1229
1230 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1231 defer ctx.deinit(allocator);
1232 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1233
1234 const loc = ir.Location.getUnknown();
1235 const vec4xf32 = try ArithDialect.getVec4xF32Type(&ctx);
1236
1237 var c1 = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32, 1.0);
1238 var c2 = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32, 2.0);
1239 const add = try ArithDialect.AddOp.create(&ctx, loc, c1.getResult(), c2.getResult());
1240
1241 try ir.verify.verifyOperation(add.op, .{ .recursive = false });
1242 }
1243
1244 test "arith.add with mismatched vector types fails verification" {
1245 const testing = std.testing;
1246 var arena = alloc_arena.Arena.init(std.testing.allocator);
1247 defer arena.deinit();
1248 const allocator = arena.allocator();
1249
1250 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1251 defer ctx.deinit(allocator);
1252 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1253
1254 const loc = ir.Location.getUnknown();
1255 const vec4xf32 = try ArithDialect.getVec4xF32Type(&ctx);
1256 const vec8xf32 = (try ArithDialect.getVecType(&ctx, 8, type_names.float32)).?;
1257
1258 var c1 = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32, 1.0);
1259 var c2 = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec8xf32, 2.0);
1260 const add = try ArithDialect.AddOp.create(&ctx, loc, c1.getResult(), c2.getResult());
1261
1262 try testing.expectError(
1263 ir.traits.TraitError.SameOperandsAndResultTypeMismatch,
1264 ir.verify.verifyOperation(add.op, .{ .recursive = false }),
1265 );
1266 }
1267
1268 test "arith.neg over matching vec4xf32 operand passes verification" {
1269 var arena = alloc_arena.Arena.init(std.testing.allocator);
1270 defer arena.deinit();
1271 const allocator = arena.allocator();
1272
1273 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1274 defer ctx.deinit(allocator);
1275 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1276
1277 const loc = ir.Location.getUnknown();
1278 const vec4xf32 = try ArithDialect.getVec4xF32Type(&ctx);
1279
1280 var c = try ArithDialect.VecConstantOp.createFloat(&ctx, loc, vec4xf32, 5.0);
1281 const neg = try ArithDialect.NegOp.create(&ctx, loc, c.getResult());
1282
1283 try ir.verify.verifyOperation(neg.op, .{ .recursive = false });
1284 }
1285
1286 test "Precision1 arith constants and typed arithmetic declarations" {
1287 const effects = ir.interfaces.effects;
1288 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
1289 defer ctx.deinit(std.testing.allocator);
1290 const scalar_types = @import("types.zig");
1291 for (std.enums.values(scalar_types.ScalarKind)) |kind| {
1292 const typ = try ArithDialect.getScalarType(&ctx, kind);
1293 const constant = if (scalar_types.scalarKindIsFloat(kind))
1294 try ArithDialect.ConstantOp.createFloat(&ctx, .unknown, typ, 1.0)
1295 else if (kind == .bool)
1296 try ArithDialect.ConstantOp.createBool(&ctx, .unknown, true)
1297 else
1298 try ArithDialect.ConstantOp.createInt(&ctx, .unknown, typ, 1);
1299 var constant_facts = try effects.inspect(std.testing.allocator, constant.op);
1300 defer constant_facts.deinit(std.testing.allocator);
1301 try std.testing.expect(effects.repeatableExpression(constant_facts.facts));
1302 const add = try ArithDialect.AddOp.create(
1303 &ctx,
1304 .unknown,
1305 constant.getResult(),
1306 constant.getResult(),
1307 );
1308 var add_facts = try effects.inspect(std.testing.allocator, add.op);
1309 defer add_facts.deinit(std.testing.allocator);
1310 try std.testing.expectEqual(
1311 kind != .bool,
1312 effects.repeatableExpression(add_facts.facts),
1313 );
1314 const div = try ArithDialect.DivOp.create(
1315 &ctx,
1316 .unknown,
1317 constant.getResult(),
1318 constant.getResult(),
1319 );
1320 var div_facts = try effects.inspect(std.testing.allocator, div.op);
1321 defer div_facts.deinit(std.testing.allocator);
1322 try std.testing.expectEqual(kind != .bool, div_facts.facts.complete);
1323 try std.testing.expectEqual(kind != .bool, effects.speculate(div_facts.facts, true));
1324 }
1325 }
1326
1327 const PrecisionRow = struct {
1328 name: []const u8,
1329 operands: usize,
1330 integer: bool = false,
1331 floating: bool = false,
1332 boolean: bool = false,
1333 };
1334
1335 const precision_rows = [_]PrecisionRow{
1336 .{ .name = "add", .operands = 2, .integer = true, .floating = true },
1337 .{ .name = "sub", .operands = 2, .integer = true, .floating = true },
1338 .{ .name = "mul", .operands = 2, .integer = true, .floating = true },
1339 .{ .name = "div", .operands = 2, .integer = true, .floating = true },
1340 .{ .name = "rem", .operands = 2, .integer = true },
1341 .{ .name = "neg", .operands = 1, .integer = true, .floating = true },
1342 .{ .name = "abs", .operands = 1, .integer = true, .floating = true },
1343 .{ .name = "min", .operands = 2, .integer = true, .floating = true },
1344 .{ .name = "max", .operands = 2, .integer = true, .floating = true },
1345 .{ .name = "and", .operands = 2, .integer = true, .boolean = true },
1346 .{ .name = "or", .operands = 2, .integer = true, .boolean = true },
1347 .{ .name = "xor", .operands = 2, .integer = true, .boolean = true },
1348 .{ .name = "not", .operands = 1, .integer = true, .boolean = true },
1349 .{ .name = "popcount", .operands = 1, .integer = true },
1350 .{ .name = "umulhi", .operands = 2, .integer = true },
1351 .{ .name = "shl", .operands = 2, .integer = true },
1352 .{ .name = "shr", .operands = 2, .integer = true },
1353 .{ .name = "ushr", .operands = 2, .integer = true },
1354 .{ .name = "fma", .operands = 3, .floating = true },
1355 .{ .name = "sqrt", .operands = 1, .floating = true },
1356 .{ .name = "exp", .operands = 1, .floating = true },
1357 .{ .name = "log", .operands = 1, .floating = true },
1358 .{ .name = "tanh", .operands = 1, .floating = true },
1359 .{ .name = "sin", .operands = 1, .floating = true },
1360 .{ .name = "cos", .operands = 1, .floating = true },
1361 .{ .name = "tan", .operands = 1, .floating = true },
1362 .{ .name = "pow", .operands = 2, .floating = true },
1363 .{ .name = "floor", .operands = 1, .floating = true },
1364 .{ .name = "round", .operands = 1, .floating = true },
1365 .{ .name = "trunc", .operands = 1, .floating = true },
1366 };
1367
1368 fn precisionOperation(
1369 ctx: *ir.Context,
1370 name: []const u8,
1371 operands: []const *ir.Value,
1372 result: ir.Type,
1373 ) !*ir.Operation {
1374 var buffer: [64]u8 = undefined;
1375 const operation_name = try std.fmt.bufPrint(&buffer, "arith.{s}", .{name});
1376 var state = ir.Operation.State.init(operation_name, .unknown);
1377 state.addOperands(operands);
1378 state.addTypes(&.{result});
1379 var builder = ir.OperationBuilder.init(ctx);
1380 return builder.create(state);
1381 }
1382
1383 fn expectPrecisionPermissions(op: *ir.Operation, permitted: bool) !void {
1384 const effects = ir.interfaces.effects;
1385 var declaration = try effects.inspect(std.testing.allocator, op);
1386 defer declaration.deinit(std.testing.allocator);
1387 try std.testing.expectEqual(permitted, effects.discard(declaration.facts));
1388 try std.testing.expectEqual(permitted, effects.duplicate(declaration.facts, .{}));
1389 try std.testing.expectEqual(permitted, effects.speculate(declaration.facts, true));
1390 try std.testing.expectEqual(permitted, effects.repeatableExpression(declaration.facts));
1391 }
1392
1393 test "Precision1 declaration roster checks every scalar type and floating policy negative" {
1394 const types = @import("types.zig");
1395 for (std.enums.values(types.ScalarKind)) |kind| {
1396 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
1397 defer ctx.deinit(std.testing.allocator);
1398 const typ = try ArithDialect.getScalarType(&ctx, kind);
1399 const value = if (types.scalarKindIsFloat(kind))
1400 (try ArithDialect.ConstantOp.createFloat(&ctx, .unknown, typ, 1.0)).getResult()
1401 else if (kind == .bool)
1402 (try ArithDialect.ConstantOp.createBool(&ctx, .unknown, true)).getResult()
1403 else
1404 (try ArithDialect.ConstantOp.createInt(&ctx, .unknown, typ, 1)).getResult();
1405 for (precision_rows) |row| {
1406 const operands = [_]*ir.Value{ value, value, value };
1407 const op = try precisionOperation(&ctx, row.name, operands[0..row.operands], typ);
1408 const expected = if (kind == .bool) row.boolean else if (types.scalarKindIsFloat(kind))
1409 row.floating
1410 else
1411 row.integer;
1412 try expectPrecisionPermissions(op, expected);
1413 ctx.arithmetic_policy.environment_observable = true;
1414 try expectPrecisionPermissions(op, expected and !types.scalarKindIsFloat(kind));
1415 ctx.arithmetic_policy.environment_observable = false;
1416 }
1417 }
1418 }
1419
1420 test "Precision1 partial integer domains refuse variable zero overflow and bad shift counts" {
1421 var block = ir.Block.init(std.testing.allocator);
1422 defer block.deinit();
1423 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
1424 defer ctx.deinit(std.testing.allocator);
1425 const typ = try ArithDialect.getI8Type(&ctx);
1426 const variable = try block.addArgument(typ, .unknown);
1427 const cases = [_]struct { rhs: i64, division: bool, shift: bool }{
1428 .{ .rhs = 0, .division = false, .shift = true },
1429 .{ .rhs = 1, .division = true, .shift = true },
1430 .{ .rhs = 2, .division = true, .shift = true },
1431 .{ .rhs = 7, .division = true, .shift = true },
1432 .{ .rhs = 8, .division = true, .shift = false },
1433 .{ .rhs = -1, .division = false, .shift = false },
1434 .{ .rhs = 255, .division = false, .shift = false },
1435 };
1436 for (cases) |case| {
1437 const rhs = (try ArithDialect.ConstantOp.createInt(
1438 &ctx,
1439 .unknown,
1440 typ,
1441 case.rhs,
1442 )).getResult();
1443 for ([_][]const u8{ "div", "rem", "shl", "shr", "ushr" }, 0..) |name, index| {
1444 const op = try precisionOperation(&ctx, name, &.{ variable, rhs }, typ);
1445 try expectPrecisionPermissions(op, if (index < 2) case.division else case.shift);
1446 }
1447 }
1448 for ([_][]const u8{ "div", "rem", "shl", "shr", "ushr" }) |name| {
1449 try expectPrecisionPermissions(
1450 try precisionOperation(&ctx, name, &.{ variable, variable }, typ),
1451 false,
1452 );
1453 }
1454 }
1455
1456 test "Precision1 floating declarations require all three Context policy fields" {
1457 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
1458 defer ctx.deinit(std.testing.allocator);
1459 const typ = try ArithDialect.getScalarType(&ctx, .f32);
1460 const input = try ArithDialect.ConstantOp.createFloat(&ctx, .unknown, typ, 4);
1461 const operation = try ArithDialect.SqrtOp.create(&ctx, .unknown, input.getResult());
1462 const Policy = ir.interfaces.effects.ArithmeticPolicy;
1463 for ([_]Policy{
1464 .{},
1465 .{ .exceptions_masked = false },
1466 .{ .default_rounding = false },
1467 .{ .environment_observable = true },
1468 }, 0..) |policy, index| {
1469 ctx.arithmetic_policy = policy;
1470 try expectPrecisionPermissions(operation.op, index == 0);
1471 }
1472 }
1473
1474 test "Precision1 structural vector declarations check lanes masks shapes and floating policy" {
1475 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
1476 defer ctx.deinit(std.testing.allocator);
1477 const scalar_type = try ArithDialect.getScalarType(&ctx, .f32);
1478 const vector_type = try ArithDialect.getVec4xF32Type(&ctx);
1479 const mask_type = (try ArithDialect.getVecType(&ctx, 4, type_names.int32)).?;
1480 const short_mask = (try ArithDialect.getVecType(&ctx, 2, type_names.int32)).?;
1481 const scalar_value = try ArithDialect.ConstantOp.createFloat(&ctx, .unknown, scalar_type, 3);
1482 const vector_value = try ArithDialect.VecConstantOp.createFloat(&ctx, .unknown, vector_type, 3);
1483 try expectPrecisionPermissions(vector_value.op, true);
1484 const splat = try ArithDialect.SplatOp.create(
1485 &ctx,
1486 .unknown,
1487 scalar_value.getResult(),
1488 vector_type,
1489 );
1490 try expectPrecisionPermissions(splat.op, true);
1491 for ([_]i64{ -1, 0, 3, 4 }) |index| {
1492 const extract = try ArithDialect.ExtractOp.create(
1493 &ctx,
1494 .unknown,
1495 vector_value.getResult(),
1496 index,
1497 scalar_type,
1498 );
1499 const insert = try ArithDialect.InsertOp.create(
1500 &ctx,
1501 .unknown,
1502 vector_value.getResult(),
1503 scalar_value.getResult(),
1504 index,
1505 );
1506 try expectPrecisionPermissions(extract.op, index >= 0 and index < 4);
1507 try expectPrecisionPermissions(insert.op, index >= 0 and index < 4);
1508 }
1509 for ([_]ir.Type{ vector_type, mask_type, short_mask }, 0..) |result, index| {
1510 const cmp = try ArithDialect.VecCmpOp.create(
1511 &ctx,
1512 .unknown,
1513 .eq,
1514 vector_value.getResult(),
1515 vector_value.getResult(),
1516 result,
1517 );
1518 try expectPrecisionPermissions(cmp.op, index < 2);
1519 ctx.arithmetic_policy.environment_observable = true;
1520 try expectPrecisionPermissions(cmp.op, false);
1521 ctx.arithmetic_policy.environment_observable = false;
1522 }
1523 for ([_][4]i64{ .{ 3, 2, 1, 0 }, .{ 0, 1, 2, 4 } }, 0..) |indices, index| {
1524 const shuffle = try ArithDialect.VecShuffleOp.create(
1525 &ctx,
1526 .unknown,
1527 vector_value.getResult(),
1528 vector_type,
1529 &indices,
1530 );
1531 try expectPrecisionPermissions(shuffle.op, index == 0);
1532 }
1533 }
1534
1535 test "Precision1 comparisons selects and scalar casts qualify only supported type shapes" {
1536 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
1537 defer ctx.deinit(std.testing.allocator);
1538 const i32_type = try ArithDialect.getScalarType(&ctx, .i32);
1539 const f32_type = try ArithDialect.getScalarType(&ctx, .f32);
1540 const i64_type = try ArithDialect.getScalarType(&ctx, .i64);
1541 const input = try ArithDialect.ConstantOp.createInt(&ctx, .unknown, i32_type, 1);
1542 const floating = try ArithDialect.ConstantOp.createFloat(&ctx, .unknown, f32_type, 1);
1543 const condition = try ArithDialect.ConstantOp.createBool(&ctx, .unknown, true);
1544 const cmp = try ArithDialect.CmpOp.create(
1545 &ctx,
1546 .unknown,
1547 .lt,
1548 input.getResult(),
1549 input.getResult(),
1550 );
1551 try expectPrecisionPermissions(cmp.op, true);
1552 const select = try ArithDialect.SelectOp.create(
1553 &ctx,
1554 .unknown,
1555 condition.getResult(),
1556 input.getResult(),
1557 input.getResult(),
1558 );
1559 try expectPrecisionPermissions(select.op, true);
1560 const cast = try ArithDialect.CastOp.create(&ctx, .unknown, input.getResult(), i64_type);
1561 try expectPrecisionPermissions(cast.op, true);
1562 const bitcast = try ArithDialect.BitcastOp.create(
1563 &ctx,
1564 .unknown,
1565 floating.getResult(),
1566 i32_type,
1567 );
1568 try expectPrecisionPermissions(bitcast.op, true);
1569 const wide = try ArithDialect.BitcastOp.create(&ctx, .unknown, input.getResult(), i64_type);
1570 try expectPrecisionPermissions(wide.op, false);
1571 const wrong = try precisionOperation(&ctx, "sub", &.{input.getResult()}, i32_type);
1572 try expectPrecisionPermissions(wrong, false);
1573 }
1574
1575 test "overflow arithmetic verifies its two results and refuses every other scalar kind" {
1576 inline for (.{ ArithDialect.AddoOp, ArithDialect.SuboOp, ArithDialect.MuloOp }) |Op| {
1577 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
1578 defer ctx.deinit(std.testing.allocator);
1579 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1580 const integer = try ArithDialect.getScalarType(&ctx, .i64);
1581 const boolean = try ArithDialect.getScalarType(&ctx, .bool);
1582 const lhs = try ArithDialect.ConstantOp.createInt(&ctx, .unknown, integer, 1);
1583 const rhs = try ArithDialect.ConstantOp.createInt(&ctx, .unknown, integer, 2);
1584 const valid = try Op.create(&ctx, .unknown, lhs.getResult(), rhs.getResult());
1585 try ir.verifyOperation(valid.op, .{});
1586 try std.testing.expect(valid.getResult().type.eql(integer));
1587 try std.testing.expect(valid.getOverflow().type.eql(boolean));
1588 try std.testing.expect(valid.op.getInterface(ir.interfaces.FoldOpInterface) == null);
1589
1590 for (std.enums.values(ScalarKind)) |kind| {
1591 if (kind == .i64) continue;
1592 const typ = try ArithDialect.getScalarType(&ctx, kind);
1593 const wrong = try ArithDialect.ConstantOp.createInt(&ctx, .unknown, typ, 1);
1594 const rejected = try Op.create(&ctx, .unknown, wrong.getResult(), wrong.getResult());
1595 try std.testing.expectError(error.OperandTypeConstraintMismatch, ir.verifyOperation(rejected.op, .{}));
1596 }
1597 const mixed = try Op.create(&ctx, .unknown, lhs.getResult(), (try ArithDialect.ConstantOp.createBool(&ctx, .unknown, false)).getResult());
1598 try std.testing.expectError(error.OperandTypeConstraintMismatch, ir.verifyOperation(mixed.op, .{}));
1599
1600 var state = ir.Operation.State.init(Op.operation_name, .unknown);
1601 state.addOperands(&.{lhs.getResult()});
1602 state.addTypes(&.{ integer, boolean });
1603 try std.testing.expectError(error.OperandCountMismatch, ir.verifyOperation(try ctx.createOperation(state), .{}));
1604 state = ir.Operation.State.init(Op.operation_name, .unknown);
1605 state.addOperands(&.{ lhs.getResult(), rhs.getResult() });
1606 state.addTypes(&.{integer});
1607 try std.testing.expectError(error.ResultCountMismatch, ir.verifyOperation(try ctx.createOperation(state), .{}));
1608 state = ir.Operation.State.init(Op.operation_name, .unknown);
1609 state.addOperands(&.{ lhs.getResult(), rhs.getResult() });
1610 state.addTypes(&.{ integer, integer });
1611 try std.testing.expectError(error.ResultTypeConstraintMismatch, ir.verifyOperation(try ctx.createOperation(state), .{}));
1612
1613 var storage: [8]ir.interfaces.effects.Fact = undefined;
1614 const facts = try ir.interfaces.effects.collectInto(valid.op, &storage);
1615 try std.testing.expect(facts.complete);
1616 try std.testing.expectEqual(@as(usize, 2), facts.records.len);
1617 for (facts.records, 0..) |fact, index| {
1618 try std.testing.expect(fact == .result);
1619 try std.testing.expectEqual(index, fact.result.index);
1620 }
1621 }
1622 }
1623
1624 test "overflow arithmetic evaluator returns value and flag without trapping" {
1625 const evaluator = @import("../../eval/root.zig");
1626 const inputs = [_]i64{ std.math.minInt(i64), std.math.minInt(i64) + 1, -9, -2, -1, 0, 1, 2, 3, 5, 9, 1 << 32, 1 << 62, std.math.maxInt(i64) };
1627 inline for (.{ ArithDialect.AddoOp, ArithDialect.SuboOp, ArithDialect.MuloOp }, 0..) |Op, kind| {
1628 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
1629 defer ctx.deinit(std.testing.allocator);
1630 try ir.dialects.loadDialectSpec(&ctx, arith.spec);
1631 const integer = try ArithDialect.getScalarType(&ctx, .i64);
1632 const lhs = try ArithDialect.ConstantOp.createInt(&ctx, .unknown, integer, 0);
1633 const rhs = try ArithDialect.ConstantOp.createInt(&ctx, .unknown, integer, 0);
1634 const operation = try Op.create(&ctx, .unknown, lhs.getResult(), rhs.getResult());
1635 const value_use = try ArithDialect.AddOp.create(&ctx, .unknown, operation.getResult(), lhs.getResult());
1636 const flag_use = try ArithDialect.SelectOp.create(&ctx, .unknown, operation.getOverflow(), lhs.getResult(), rhs.getResult());
1637 var machine = evaluator.Evaluator.init(std.testing.allocator, &ctx);
1638 defer machine.deinit();
1639 for (inputs) |a| for (inputs) |b| {
1640 try machine.setValue(lhs.getResult(), try ctx.getI64Attr(a));
1641 try machine.setValue(rhs.getResult(), try ctx.getI64Attr(b));
1642 const expected = switch (kind) {
1643 0 => @addWithOverflow(a, b),
1644 1 => @subWithOverflow(a, b),
1645 2 => @mulWithOverflow(a, b),
1646 else => unreachable,
1647 };
1648 const answer = try machine.evaluate(operation.op);
1649 const values = answer.interface(ir.interfaces.AttributeArrayInterface).?;
1650 try std.testing.expectEqual(@as(usize, 2), values.call(.getCount, .{}));
1651 try std.testing.expectEqual(expected[0], ArithDialect.getIntValue(values.call(.getElement, .{@as(usize, 0)}).?).?);
1652 try std.testing.expectEqual(expected[1] != 0, ArithDialect.getBoolValue(values.call(.getElement, .{@as(usize, 1)}).?).?);
1653 };
1654 try machine.setValue(lhs.getResult(), try ctx.getI64Attr(std.math.maxInt(i64)));
1655 try machine.setValue(rhs.getResult(), try ctx.getI64Attr(2));
1656 _ = try machine.evaluate(value_use.op);
1657 _ = try machine.evaluate(flag_use.op);
1658 try std.testing.expect(machine.getValue(operation.getResult()) != null);
1659 try std.testing.expect(machine.getValue(operation.getOverflow()) != null);
1660 }
1661 }
1662
1663 test "overflow arithmetic preserves both results through cloning text CSE and dead code removal" {
1664 const dialects = @import("../root.zig");
1665 const passes = @import("../../passes/root.zig");
1666 inline for (.{ ArithDialect.AddoOp, ArithDialect.SuboOp, ArithDialect.MuloOp }) |Op| {
1667 for (0..4) |used| {
1668 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
1669 defer ctx.deinit(std.testing.allocator);
1670 try dialects.registerAllDialects(&ctx);
1671 const integer = try ArithDialect.getScalarType(&ctx, .i64);
1672 const boolean = try ArithDialect.getScalarType(&ctx, .bool);
1673 const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, .unknown);
1674 var result_types: [2]ir.Type = undefined;
1675 var count: usize = 0;
1676 if (used & 1 != 0) {
1677 result_types[count] = integer;
1678 count += 1;
1679 }
1680 if (used & 2 != 0) {
1681 result_types[count] = boolean;
1682 count += 1;
1683 }
1684 const function = try dialects.FuncDialect.FuncOp.create(&ctx, .unknown, "checked", &.{ integer, integer }, result_types[0..count]);
1685 try module.getBodyBlock().addOperation(function.op);
1686 const block = function.getEntryBlock();
1687 const first = try Op.create(&ctx, .unknown, function.getArgument(0), function.getArgument(1));
1688 const second = try Op.create(&ctx, .unknown, function.getArgument(0), function.getArgument(1));
1689 try block.addOperation(first.op);
1690 try block.addOperation(second.op);
1691 var returns: [2]*ir.Value = undefined;
1692 count = 0;
1693 if (used & 1 != 0) {
1694 returns[count] = second.getResult();
1695 count += 1;
1696 }
1697 if (used & 2 != 0) {
1698 returns[count] = second.getOverflow();
1699 count += 1;
1700 }
1701 const ret = try dialects.FuncDialect.ReturnOp.create(&ctx, .unknown, returns[0..count]);
1702 try block.addOperation(ret.op);
1703 try ir.verifyOperation(module.op, .{});
1704 const clone = try module.op.clone();
1705 try ir.verifyOperation(clone, .{});
1706 const text = try ir.dump.operationAlloc(std.testing.allocator, clone);
1707 defer std.testing.allocator.free(text);
1708 const parsed = try ir.parse.operation(&ctx, text);
1709 try ir.verifyOperation(parsed, .{});
1710 const round_trip = try ir.dump.operationAlloc(std.testing.allocator, parsed);
1711 defer std.testing.allocator.free(round_trip);
1712 try std.testing.expectEqualStrings(text, round_trip);
1713 var manager = passes.PassManager.init(std.testing.allocator);
1714 defer manager.deinit();
1715 try manager.addPass(passes.createCommonSubexpressionEliminationPass());
1716 try manager.addPass(passes.createDeadCodeEliminationPass());
1717 try std.testing.expectEqual(passes.PassResult.success, manager.run(module.op, &ctx));
1718 try ir.verifyOperation(module.op, .{});
1719 var operations = block.getOperations();
1720 if (used != 0) {
1721 try std.testing.expect(operations.next().? == first.op);
1722 for (ret.op.operands.items) |operand| {
1723 try std.testing.expect(operand.value.getDefiningOp().? == @as(*anyopaque, @ptrCast(first.op)));
1724 }
1725 }
1726 try std.testing.expect(operations.next().? == ret.op);
1727 try std.testing.expect(operations.next() == null);
1728 }
1729 }
1730 }
1731
1732 test "overflow arithmetic constant roots keep both results through the fold cache" {
1733 const dialects = @import("../root.zig");
1734 const passes = @import("../../passes/root.zig");
1735 inline for (.{ ArithDialect.AddoOp, ArithDialect.SuboOp, ArithDialect.MuloOp }) |Op| {
1736 for (1..4) |used| {
1737 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
1738 defer ctx.deinit(std.testing.allocator);
1739 try dialects.registerAllDialects(&ctx);
1740 const integer = try ArithDialect.getScalarType(&ctx, .i64);
1741 const boolean = try ArithDialect.getScalarType(&ctx, .bool);
1742 const types: []const ir.Type = switch (used) {
1743 1 => &.{integer},
1744 2 => &.{boolean},
1745 3 => &.{ integer, boolean },
1746 else => unreachable,
1747 };
1748 const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, .unknown);
1749 const function = try dialects.FuncDialect.FuncOp.create(&ctx, .unknown, "constants", &.{}, types);
1750 try module.getBodyBlock().addOperation(function.op);
1751 const block = function.getEntryBlock();
1752 const lhs = try ArithDialect.ConstantOp.createInt(&ctx, .unknown, integer, std.math.maxInt(i64));
1753 const rhs = try ArithDialect.ConstantOp.createInt(&ctx, .unknown, integer, 2);
1754 try block.addOperation(lhs.op);
1755 try block.addOperation(rhs.op);
1756 const checked = try Op.create(&ctx, .unknown, lhs.getResult(), rhs.getResult());
1757 try block.addOperation(checked.op);
1758 const values: []const *ir.Value = switch (used) {
1759 1 => &.{checked.getResult()},
1760 2 => &.{checked.getOverflow()},
1761 3 => &.{ checked.getResult(), checked.getOverflow() },
1762 else => unreachable,
1763 };
1764 const ret = try dialects.FuncDialect.ReturnOp.create(&ctx, .unknown, values);
1765 try block.addOperation(ret.op);
1766 var manager = passes.PassManager.init(std.testing.allocator);
1767 defer manager.deinit();
1768 try manager.addPass(passes.createCanonicalizationPass());
1769 try manager.addPass(passes.createConstantFoldingPass());
1770 try manager.addPass(passes.createCommonSubexpressionEliminationPass());
1771 try manager.addPass(passes.createDeadCodeEliminationPass());
1772 try std.testing.expectEqual(passes.PassResult.success, manager.run(module.op, &ctx));
1773 try ir.verifyOperation(module.op, .{});
1774 try std.testing.expectEqual(@as(usize, 2), checked.op.getNumResults());
1775 try std.testing.expect(checked.getResult().type.eql(integer));
1776 try std.testing.expect(checked.getOverflow().type.eql(boolean));
1777 try std.testing.expectEqualSlices(*ir.Value, values, ret.op.getOperandValues());
1778 var operations = block.getOperations();
1779 try std.testing.expect(operations.next().? == lhs.op);
1780 try std.testing.expect(operations.next().? == rhs.op);
1781 try std.testing.expect(operations.next().? == checked.op);
1782 try std.testing.expect(operations.next().? == ret.op);
1783 try std.testing.expect(operations.next() == null);
1784 }
1785 }
1786 }