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 }