lib/choir/src/backends/gpu/spirv/emitter/scalar.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const std = @import("std");
   2 const choir = @import("../../../../root.zig");
   3 
   4 const ir = choir.ir;
   5 const dialects = choir.dialects;
   6 const binary = @import("module.zig");
   7 const spirv_ops = @import("ops.zig");
   8 
   9 const arith = dialects.arith;
  10 const ArithDialect = arith.ArithDialect;
  11 const ModuleBuilder = binary.Builder;
  12 const SpirvOp = spirv_ops.SpirvOp;
  13 
  14 pub const GLSLstd450 = struct {
  15     pub const Trunc: u32 = 3;
  16     pub const FAbs: u32 = 4;
  17     pub const SAbs: u32 = 5;
  18     pub const Floor: u32 = 8;
  19     pub const Sin: u32 = 13;
  20     pub const Cos: u32 = 14;
  21     pub const Tan: u32 = 15;
  22     pub const Tanh: u32 = 21;
  23     pub const Atan2: u32 = 25;
  24     pub const Pow: u32 = 26;
  25     pub const Exp: u32 = 27;
  26     pub const Log: u32 = 28;
  27     pub const Sqrt: u32 = 31;
  28     pub const Fma: u32 = 50;
  29     pub const FMin: u32 = 37;
  30     pub const UMin: u32 = 38;
  31     pub const SMin: u32 = 39;
  32     pub const FMax: u32 = 40;
  33     pub const UMax: u32 = 41;
  34     pub const SMax: u32 = 42;
  35     pub const NMin: u32 = 79;
  36     pub const NMax: u32 = 80;
  37 };
  38 
  39 pub const Kind = enum {
  40     void,
  41     bool,
  42     i8,
  43     i16,
  44     i32,
  45     i64,
  46     u8,
  47     u16,
  48     u32,
  49     u64,
  50     f16,
  51     f32,
  52     f64,
  53 };
  54 
  55 pub fn kindFromName(name: []const u8) ?Kind {
  56     const scalar_kind = arith.scalarKindFromTypeName(name) orelse return null;
  57     return switch (scalar_kind) {
  58         .bool => .bool,
  59         .i8 => .i8,
  60         .i16 => .i16,
  61         .i32 => .i32,
  62         .i64 => .i64,
  63         .u8 => .u8,
  64         .u16 => .u16,
  65         .u32 => .u32,
  66         .u64 => .u64,
  67         .f16 => .f16,
  68         .bf16 => null,
  69         .f32 => .f32,
  70         .f64 => .f64,
  71         .index => .u32,
  72     };
  73 }
  74 
  75 pub fn kindFromType(ty: ir.Type) ?Kind {
  76     const name = ty.getDialectTypeName() orelse return null;
  77     return kindFromName(name);
  78 }
  79 
  80 pub fn emitConstant(self: anytype, op: *ir.Operation) !void {
  81     const result = op.getResult(0) orelse return error.UnsupportedOperation;
  82     const type_id = try self.getTypeForValue(result);
  83 
  84     const constant = ArithDialect.ConstantOp{ .op = op };
  85 
  86     if (constant.getIntValue()) |int_value| {
  87         const kind = kindFromType(result.type) orelse return error.UnsupportedType;
  88         const const_id = try self.getIntConstant(type_id, kind, int_value);
  89         try self.bindValue(result, const_id);
  90         return;
  91     }
  92 
  93     if (constant.getFloatValue()) |float_value| {
  94         const kind = kindFromType(result.type) orelse return error.UnsupportedType;
  95         const const_id = try self.getFloatConstant(type_id, kind, float_value);
  96         try self.bindValue(result, const_id);
  97         return;
  98     }
  99 
 100     if (op.getAttrAs(ir.Attribute.BoolAttr, "value")) |bool_attr| {
 101         const const_id = try self.getBoolConstant(bool_attr.getValue());
 102         try self.bindValue(result, const_id);
 103         return;
 104     }
 105 
 106     return error.UnsupportedOperation;
 107 }
 108 
 109 pub const BinaryKind = enum { add, sub, mul, div };
 110 
 111 pub fn emitBinaryArith(self: anytype, op: *ir.Operation, kind: BinaryKind) !void {
 112     if (op.operands.items.len != 2) return error.UnsupportedOperation;
 113     const lhs = op.operands.items[0].value;
 114     const rhs = op.operands.items[1].value;
 115     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 116 
 117     const lhs_id = try self.getValue(lhs);
 118     const rhs_id = try self.getValue(rhs);
 119     const result_type_id = try self.getTypeForValue(result);
 120 
 121     const type_kind = kindFromType(result.type) orelse return error.UnsupportedType;
 122     const opcode = switch (type_kind) {
 123         .f16, .f32, .f64 => switch (kind) {
 124             .add => SpirvOp.FAdd,
 125             .sub => SpirvOp.FSub,
 126             .mul => SpirvOp.FMul,
 127             .div => SpirvOp.FDiv,
 128         },
 129         .i8, .i16, .i32, .i64 => switch (kind) {
 130             .add => SpirvOp.IAdd,
 131             .sub => SpirvOp.ISub,
 132             .mul => SpirvOp.IMul,
 133             .div => SpirvOp.SDiv,
 134         },
 135         .u8, .u16, .u32, .u64 => switch (kind) {
 136             .add => SpirvOp.IAdd,
 137             .sub => SpirvOp.ISub,
 138             .mul => SpirvOp.IMul,
 139             .div => SpirvOp.UDiv,
 140         },
 141         else => return error.UnsupportedType,
 142     };
 143 
 144     const result_id = self.builder.newId();
 145     try self.builder.emit(&self.builder.functions, opcode, &.{
 146         result_type_id,
 147         result_id,
 148         lhs_id,
 149         rhs_id,
 150     });
 151     try self.bindValue(result, result_id);
 152 }
 153 
 154 pub fn emitBinaryArithIds(
 155     self: anytype,
 156     kind: Kind,
 157     lhs_id: u32,
 158     rhs_id: u32,
 159     op_kind: BinaryKind,
 160 ) !u32 {
 161     const result_type_id = try self.getScalarType(kind);
 162     const opcode = switch (kind) {
 163         .f16, .f32, .f64 => switch (op_kind) {
 164             .add => SpirvOp.FAdd,
 165             .sub => SpirvOp.FSub,
 166             .mul => SpirvOp.FMul,
 167             .div => SpirvOp.FDiv,
 168         },
 169         .i8, .i16, .i32, .i64 => switch (op_kind) {
 170             .add => SpirvOp.IAdd,
 171             .sub => SpirvOp.ISub,
 172             .mul => SpirvOp.IMul,
 173             .div => SpirvOp.SDiv,
 174         },
 175         .u8, .u16, .u32, .u64 => switch (op_kind) {
 176             .add => SpirvOp.IAdd,
 177             .sub => SpirvOp.ISub,
 178             .mul => SpirvOp.IMul,
 179             .div => SpirvOp.UDiv,
 180         },
 181         else => return error.UnsupportedType,
 182     };
 183 
 184     const result_id = self.builder.newId();
 185     try self.builder.emit(&self.builder.functions, opcode, &.{
 186         result_type_id,
 187         result_id,
 188         lhs_id,
 189         rhs_id,
 190     });
 191     return result_id;
 192 }
 193 
 194 pub fn emitNegArith(self: anytype, op: *ir.Operation) !void {
 195     if (op.operands.items.len != 1) return error.UnsupportedOperation;
 196     const input = op.operands.items[0].value;
 197     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 198 
 199     const input_id = try self.getValue(input);
 200     const result_type_id = try self.getTypeForValue(result);
 201 
 202     const kind = kindFromType(result.type) orelse return error.UnsupportedType;
 203     const result_id = self.builder.newId();
 204     if (isUnsignedInt(kind)) {
 205         const zero_id = try self.getIntConstant(result_type_id, kind, 0);
 206         try self.builder.emit(&self.builder.functions, SpirvOp.ISub, &.{
 207             result_type_id,
 208             result_id,
 209             zero_id,
 210             input_id,
 211         });
 212     } else {
 213         const opcode = if (isFloat(kind))
 214             SpirvOp.FNegate
 215         else if (isSignedInt(kind))
 216             SpirvOp.SNegate
 217         else
 218             return error.UnsupportedType;
 219         try self.builder.emit(&self.builder.functions, opcode, &.{
 220             result_type_id,
 221             result_id,
 222             input_id,
 223         });
 224     }
 225     try self.bindValue(result, result_id);
 226 }
 227 
 228 pub const FloatExtConstraint = enum {
 229     float16_or_32,
 230     any_float,
 231 };
 232 
 233 const RoundBits = struct {
 234     kind: Kind,
 235     sign_mask: i64,
 236     magnitude_mask: i64,
 237 };
 238 
 239 fn floatKindAllowedFor(constraint: FloatExtConstraint, kind: Kind) bool {
 240     return switch (constraint) {
 241         .float16_or_32 => kind == .f16 or kind == .f32,
 242         .any_float => isFloat(kind),
 243     };
 244 }
 245 
 246 fn roundBitsFor(kind: Kind) ?RoundBits {
 247     return switch (kind) {
 248         .f16 => .{ .kind = .u16, .sign_mask = 0x8000, .magnitude_mask = 0x7fff },
 249         .f32 => .{ .kind = .u32, .sign_mask = 0x80000000, .magnitude_mask = 0x7fffffff },
 250         else => null,
 251     };
 252 }
 253 
 254 pub fn emitGlslExtUnary(
 255     self: anytype,
 256     op: *ir.Operation,
 257     ext_opcode: u32,
 258     constraint: FloatExtConstraint,
 259 ) !void {
 260     if (op.operands.items.len != 1) return error.UnsupportedOperation;
 261     const input = op.operands.items[0].value;
 262     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 263 
 264     const kind = kindFromType(result.type) orelse return error.UnsupportedType;
 265     if (!floatKindAllowedFor(constraint, kind)) return error.UnsupportedType;
 266 
 267     const input_id = try self.getValue(input);
 268     const result_type_id = try self.getTypeForValue(result);
 269     const set_id = try self.builder.importGlslStd450();
 270 
 271     const result_id = self.builder.newId();
 272     try self.builder.emit(&self.builder.functions, SpirvOp.ExtInst, &.{
 273         result_type_id,
 274         result_id,
 275         set_id,
 276         ext_opcode,
 277         input_id,
 278     });
 279     try self.builder.emitNoContraction(result_id);
 280     try self.bindValue(result, result_id);
 281 }
 282 
 283 pub fn emitRoundArith(self: anytype, op: *ir.Operation) !void {
 284     if (op.operands.items.len != 1) return error.UnsupportedOperation;
 285     const input = op.operands.items[0].value;
 286     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 287 
 288     const kind = kindFromType(result.type) orelse return error.UnsupportedType;
 289     const input_kind = kindFromType(input.type) orelse return error.UnsupportedType;
 290     if (kind != input_kind) return error.UnsupportedType;
 291     const bits = roundBitsFor(kind) orelse return error.UnsupportedType;
 292 
 293     const input_id = try self.getValue(input);
 294     const float_type_id = try self.getScalarType(kind);
 295     const int_type_id = try self.getScalarType(bits.kind);
 296     const set_id = try self.builder.importGlslStd450();
 297 
 298     const abs_id = self.builder.newId();
 299     try self.builder.emit(&self.builder.functions, SpirvOp.ExtInst, &.{
 300         float_type_id,
 301         abs_id,
 302         set_id,
 303         GLSLstd450.FAbs,
 304         input_id,
 305     });
 306     try self.builder.emitNoContraction(abs_id);
 307 
 308     const half_id = try self.getFloatConstant(float_type_id, kind, 0.5);
 309     const shifted_id = self.builder.newId();
 310     try self.builder.emit(&self.builder.functions, SpirvOp.FAdd, &.{
 311         float_type_id,
 312         shifted_id,
 313         abs_id,
 314         half_id,
 315     });
 316 
 317     const rounded_abs_id = self.builder.newId();
 318     try self.builder.emit(&self.builder.functions, SpirvOp.ExtInst, &.{
 319         float_type_id,
 320         rounded_abs_id,
 321         set_id,
 322         GLSLstd450.Floor,
 323         shifted_id,
 324     });
 325     try self.builder.emitNoContraction(rounded_abs_id);
 326 
 327     const input_bits_id = self.builder.newId();
 328     try self.builder.emit(&self.builder.functions, SpirvOp.Bitcast, &.{
 329         int_type_id,
 330         input_bits_id,
 331         input_id,
 332     });
 333 
 334     const rounded_bits_id = self.builder.newId();
 335     try self.builder.emit(&self.builder.functions, SpirvOp.Bitcast, &.{
 336         int_type_id,
 337         rounded_bits_id,
 338         rounded_abs_id,
 339     });
 340 
 341     const sign_mask_id = try self.getIntConstant(int_type_id, bits.kind, bits.sign_mask);
 342     const sign_bits_id = self.builder.newId();
 343     try self.builder.emit(&self.builder.functions, SpirvOp.BitwiseAnd, &.{
 344         int_type_id,
 345         sign_bits_id,
 346         input_bits_id,
 347         sign_mask_id,
 348     });
 349 
 350     const magnitude_mask_id = try self.getIntConstant(int_type_id, bits.kind, bits.magnitude_mask);
 351     const magnitude_bits_id = self.builder.newId();
 352     try self.builder.emit(&self.builder.functions, SpirvOp.BitwiseAnd, &.{
 353         int_type_id,
 354         magnitude_bits_id,
 355         rounded_bits_id,
 356         magnitude_mask_id,
 357     });
 358 
 359     const result_bits_id = self.builder.newId();
 360     try self.builder.emit(&self.builder.functions, SpirvOp.BitwiseOr, &.{
 361         int_type_id,
 362         result_bits_id,
 363         magnitude_bits_id,
 364         sign_bits_id,
 365     });
 366 
 367     const result_id = self.builder.newId();
 368     try self.builder.emit(&self.builder.functions, SpirvOp.Bitcast, &.{
 369         float_type_id,
 370         result_id,
 371         result_bits_id,
 372     });
 373     try self.bindValue(result, result_id);
 374 }
 375 
 376 fn emitGlslExtBinary(
 377     self: anytype,
 378     op: *ir.Operation,
 379     ext_opcode: u32,
 380     constraint: FloatExtConstraint,
 381 ) !void {
 382     if (op.operands.items.len != 2) return error.UnsupportedOperation;
 383     const lhs = op.operands.items[0].value;
 384     const rhs = op.operands.items[1].value;
 385     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 386 
 387     const kind = kindFromType(result.type) orelse return error.UnsupportedType;
 388     if (!floatKindAllowedFor(constraint, kind)) return error.UnsupportedType;
 389     if (!lhs.type.eql(result.type)) return error.UnsupportedType;
 390     if (!rhs.type.eql(result.type)) return error.UnsupportedType;
 391 
 392     const lhs_id = try self.getValue(lhs);
 393     const rhs_id = try self.getValue(rhs);
 394     const result_type_id = try self.getTypeForValue(result);
 395     const set_id = try self.builder.importGlslStd450();
 396 
 397     const result_id = self.builder.newId();
 398     try self.builder.emit(&self.builder.functions, SpirvOp.ExtInst, &.{
 399         result_type_id,
 400         result_id,
 401         set_id,
 402         ext_opcode,
 403         lhs_id,
 404         rhs_id,
 405     });
 406     try self.builder.emitNoContraction(result_id);
 407     try self.bindValue(result, result_id);
 408 }
 409 
 410 pub fn emitPowArith(self: anytype, op: *ir.Operation) !void {
 411     return emitGlslExtBinary(self, op, GLSLstd450.Pow, .float16_or_32);
 412 }
 413 
 414 pub fn emitAtan2Arith(self: anytype, op: *ir.Operation) !void {
 415     return emitGlslExtBinary(self, op, GLSLstd450.Atan2, .float16_or_32);
 416 }
 417 
 418 fn emitGlslExtTernary(
 419     self: anytype,
 420     op: *ir.Operation,
 421     ext_opcode: u32,
 422     constraint: FloatExtConstraint,
 423 ) !void {
 424     if (op.operands.items.len != 3) return error.UnsupportedOperation;
 425     const a = op.operands.items[0].value;
 426     const b = op.operands.items[1].value;
 427     const c = op.operands.items[2].value;
 428     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 429 
 430     const kind = kindFromType(result.type) orelse return error.UnsupportedType;
 431     if (!floatKindAllowedFor(constraint, kind)) return error.UnsupportedType;
 432     if (!a.type.eql(result.type)) return error.UnsupportedType;
 433     if (!b.type.eql(result.type)) return error.UnsupportedType;
 434     if (!c.type.eql(result.type)) return error.UnsupportedType;
 435 
 436     const a_id = try self.getValue(a);
 437     const b_id = try self.getValue(b);
 438     const c_id = try self.getValue(c);
 439     const result_type_id = try self.getTypeForValue(result);
 440     const set_id = try self.builder.importGlslStd450();
 441 
 442     const result_id = self.builder.newId();
 443     try self.builder.emit(&self.builder.functions, SpirvOp.ExtInst, &.{
 444         result_type_id,
 445         result_id,
 446         set_id,
 447         ext_opcode,
 448         a_id,
 449         b_id,
 450         c_id,
 451     });
 452     try self.builder.emitNoContraction(result_id);
 453     try self.bindValue(result, result_id);
 454 }
 455 
 456 pub fn emitFmaArith(self: anytype, op: *ir.Operation) !void {
 457     return emitGlslExtTernary(self, op, GLSLstd450.Fma, .any_float);
 458 }
 459 
 460 pub fn emitSqrtArith(self: anytype, op: *ir.Operation) !void {
 461     return emitGlslExtUnary(self, op, GLSLstd450.Sqrt, .any_float);
 462 }
 463 
 464 pub fn emitAbsArith(self: anytype, op: *ir.Operation) !void {
 465     if (op.operands.items.len != 1) return error.UnsupportedOperation;
 466     const input = op.operands.items[0].value;
 467     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 468 
 469     const kind = kindFromType(result.type) orelse return error.UnsupportedType;
 470     const input_id = try self.getValue(input);
 471     if (isUnsignedInt(kind)) {
 472         try self.bindValue(result, input_id);
 473         return;
 474     }
 475 
 476     const ext_opcode: u32 = if (isFloat(kind))
 477         GLSLstd450.FAbs
 478     else if (isSignedInt(kind))
 479         GLSLstd450.SAbs
 480     else
 481         return error.UnsupportedType;
 482 
 483     const result_type_id = try self.getTypeForValue(result);
 484     const set_id = try self.builder.importGlslStd450();
 485 
 486     const result_id = self.builder.newId();
 487     try self.builder.emit(&self.builder.functions, SpirvOp.ExtInst, &.{
 488         result_type_id,
 489         result_id,
 490         set_id,
 491         ext_opcode,
 492         input_id,
 493     });
 494     if (isFloat(kind)) try self.builder.emitNoContraction(result_id);
 495     try self.bindValue(result, result_id);
 496 }
 497 
 498 pub const BitwiseBinaryKind = enum { band, bor, bxor };
 499 pub const ShiftKind = enum { shl, shr, ushr };
 500 
 501 pub fn emitBitwiseBinary(
 502     self: anytype,
 503     op: *ir.Operation,
 504     kind: BitwiseBinaryKind,
 505 ) !void {
 506     if (op.operands.items.len != 2) return error.UnsupportedOperation;
 507     const lhs = op.operands.items[0].value;
 508     const rhs = op.operands.items[1].value;
 509     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 510 
 511     const result_kind = kindFromType(result.type) orelse return error.UnsupportedType;
 512     const lhs_kind = kindFromType(lhs.type) orelse return error.UnsupportedType;
 513     const rhs_kind = kindFromType(rhs.type) orelse return error.UnsupportedType;
 514     if (result_kind != lhs_kind or result_kind != rhs_kind) return error.UnsupportedType;
 515     const opcode: u16 = if (result_kind == .bool) switch (kind) {
 516         .band => SpirvOp.LogicalAnd,
 517         .bor => SpirvOp.LogicalOr,
 518         .bxor => SpirvOp.LogicalNotEqual,
 519     } else blk: {
 520         if (!isInt(result_kind)) return error.UnsupportedType;
 521         break :blk switch (kind) {
 522             .band => SpirvOp.BitwiseAnd,
 523             .bor => SpirvOp.BitwiseOr,
 524             .bxor => SpirvOp.BitwiseXor,
 525         };
 526     };
 527 
 528     const lhs_id = try self.getValue(lhs);
 529     const rhs_id = try self.getValue(rhs);
 530     const result_type_id = try self.getTypeForValue(result);
 531 
 532     const result_id = self.builder.newId();
 533     try self.builder.emit(&self.builder.functions, opcode, &.{
 534         result_type_id,
 535         result_id,
 536         lhs_id,
 537         rhs_id,
 538     });
 539     try self.bindValue(result, result_id);
 540 }
 541 
 542 pub fn emitNotArith(self: anytype, op: *ir.Operation) !void {
 543     if (op.operands.items.len != 1) return error.UnsupportedOperation;
 544     const input = op.operands.items[0].value;
 545     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 546 
 547     const result_kind = kindFromType(result.type) orelse return error.UnsupportedType;
 548     const input_kind = kindFromType(input.type) orelse return error.UnsupportedType;
 549     if (result_kind != input_kind) return error.UnsupportedType;
 550     const opcode: u16 = switch (result_kind) {
 551         .bool => SpirvOp.LogicalNot,
 552         else => blk: {
 553             if (!isInt(result_kind)) return error.UnsupportedType;
 554             break :blk SpirvOp.Not;
 555         },
 556     };
 557 
 558     const input_id = try self.getValue(input);
 559     const result_type_id = try self.getTypeForValue(result);
 560 
 561     const result_id = self.builder.newId();
 562     try self.builder.emit(&self.builder.functions, opcode, &.{
 563         result_type_id,
 564         result_id,
 565         input_id,
 566     });
 567     try self.bindValue(result, result_id);
 568 }
 569 
 570 pub fn emitShiftArith(
 571     self: anytype,
 572     op: *ir.Operation,
 573     kind: ShiftKind,
 574 ) !void {
 575     if (op.operands.items.len != 2) return error.UnsupportedOperation;
 576     const value = op.operands.items[0].value;
 577     const count = op.operands.items[1].value;
 578     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 579 
 580     const result_kind = kindFromType(result.type) orelse return error.UnsupportedType;
 581     const value_kind = kindFromType(value.type) orelse return error.UnsupportedType;
 582     const count_kind = kindFromType(count.type) orelse return error.UnsupportedType;
 583     if (result_kind != value_kind or result_kind != count_kind) return error.UnsupportedType;
 584     if (!isInt(result_kind)) return error.UnsupportedType;
 585 
 586     if (kind == .shr and !isSignedInt(result_kind)) return error.UnsupportedType;
 587 
 588     const opcode: u16 = switch (kind) {
 589         .shl => SpirvOp.ShiftLeftLogical,
 590         .shr => SpirvOp.ShiftRightArithmetic,
 591         .ushr => SpirvOp.ShiftRightLogical,
 592     };
 593 
 594     const value_id = try self.getValue(value);
 595     const count_id = try self.getValue(count);
 596     const result_type_id = try self.getTypeForValue(result);
 597 
 598     const result_id = self.builder.newId();
 599     try self.builder.emit(&self.builder.functions, opcode, &.{
 600         result_type_id,
 601         result_id,
 602         value_id,
 603         count_id,
 604     });
 605     try self.bindValue(result, result_id);
 606 }
 607 
 608 pub fn emitUmulhi(self: anytype, op: *ir.Operation) !void {
 609     if (op.operands.items.len != 2) return error.UnsupportedOperation;
 610     const lhs = op.operands.items[0].value;
 611     const rhs = op.operands.items[1].value;
 612     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 613 
 614     const result_kind = kindFromType(result.type) orelse return error.UnsupportedType;
 615     const lhs_kind = kindFromType(lhs.type) orelse return error.UnsupportedType;
 616     const rhs_kind = kindFromType(rhs.type) orelse return error.UnsupportedType;
 617     if (result_kind != lhs_kind or result_kind != rhs_kind) return error.UnsupportedType;
 618     if (!isInt(result_kind)) return error.UnsupportedType;
 619 
 620     const lhs_id = try self.getValue(lhs);
 621     const rhs_id = try self.getValue(rhs);
 622     const result_type_id = try self.getTypeForValue(result);
 623     const pair_type_id = try self.getPairStructType(result_type_id);
 624 
 625     const pair_id = self.builder.newId();
 626     try self.builder.emit(&self.builder.functions, SpirvOp.UMulExtended, &.{
 627         pair_type_id,
 628         pair_id,
 629         lhs_id,
 630         rhs_id,
 631     });
 632 
 633     const result_id = self.builder.newId();
 634     try self.builder.emit(&self.builder.functions, SpirvOp.CompositeExtract, &.{
 635         result_type_id,
 636         result_id,
 637         pair_id,
 638         1,
 639     });
 640     try self.bindValue(result, result_id);
 641 }
 642 
 643 pub const MinMaxKind = enum { max, min };
 644 
 645 pub fn emitMinMaxArith(
 646     self: anytype,
 647     op: *ir.Operation,
 648     kind_op: MinMaxKind,
 649 ) !void {
 650     if (op.operands.items.len != 2) return error.UnsupportedOperation;
 651     const lhs = op.operands.items[0].value;
 652     const rhs = op.operands.items[1].value;
 653     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 654 
 655     const lhs_id = try self.getValue(lhs);
 656     const rhs_id = try self.getValue(rhs);
 657     const result_type_id = try self.getTypeForValue(result);
 658 
 659     const scalar_kind = kindFromType(result.type) orelse return error.UnsupportedType;
 660     const ext_opcode: u32 = if (isFloat(scalar_kind))
 661         switch (kind_op) {
 662             .max => GLSLstd450.NMax,
 663             .min => GLSLstd450.NMin,
 664         }
 665     else if (isSignedInt(scalar_kind))
 666         switch (kind_op) {
 667             .max => GLSLstd450.SMax,
 668             .min => GLSLstd450.SMin,
 669         }
 670     else if (isUnsignedInt(scalar_kind))
 671         switch (kind_op) {
 672             .max => GLSLstd450.UMax,
 673             .min => GLSLstd450.UMin,
 674         }
 675     else
 676         return error.UnsupportedType;
 677 
 678     const set_id = try self.builder.importGlslStd450();
 679     const result_id = self.builder.newId();
 680     try self.builder.emit(&self.builder.functions, SpirvOp.ExtInst, &.{
 681         result_type_id,
 682         result_id,
 683         set_id,
 684         ext_opcode,
 685         lhs_id,
 686         rhs_id,
 687     });
 688     if (isFloat(scalar_kind)) try self.builder.emitNoContraction(result_id);
 689     try self.bindValue(result, result_id);
 690 }
 691 
 692 pub fn emitCast(self: anytype, op: *ir.Operation) !void {
 693     if (op.operands.items.len != 1) return error.UnsupportedOperation;
 694     const input = op.operands.items[0].value;
 695     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 696 
 697     const input_kind = kindFromType(input.type) orelse return error.UnsupportedType;
 698     const result_kind = kindFromType(result.type) orelse return error.UnsupportedType;
 699     const input_id = try self.getValue(input);
 700 
 701     if (input_kind == result_kind) {
 702         try self.bindValue(result, input_id);
 703         return;
 704     }
 705 
 706     const result_type_id = try self.getScalarType(result_kind);
 707 
 708     if (input_kind == .bool and result_kind != .bool) {
 709         const result_id = self.builder.newId();
 710         if (isFloat(result_kind)) {
 711             const zero = try self.getFloatConstant(result_type_id, result_kind, 0.0);
 712             const one = try self.getFloatConstant(result_type_id, result_kind, 1.0);
 713             try self.builder.emit(&self.builder.functions, SpirvOp.Select, &.{
 714                 result_type_id,
 715                 result_id,
 716                 input_id,
 717                 one,
 718                 zero,
 719             });
 720         } else {
 721             const zero = try self.getIntConstant(result_type_id, result_kind, 0);
 722             const one = try self.getIntConstant(result_type_id, result_kind, 1);
 723             try self.builder.emit(&self.builder.functions, SpirvOp.Select, &.{
 724                 result_type_id,
 725                 result_id,
 726                 input_id,
 727                 one,
 728                 zero,
 729             });
 730         }
 731         try self.bindValue(result, result_id);
 732         return;
 733     }
 734 
 735     if (result_kind == .bool and input_kind != .bool) {
 736         const bool_type_id = try self.getScalarType(.bool);
 737         const result_id = self.builder.newId();
 738         if (isFloat(input_kind)) {
 739             const zero = try self.getFloatConstant(try self.getScalarType(input_kind), input_kind, 0.0);
 740             try self.builder.emit(&self.builder.functions, SpirvOp.FOrdNotEqual, &.{
 741                 bool_type_id,
 742                 result_id,
 743                 input_id,
 744                 zero,
 745             });
 746         } else {
 747             const zero = try self.getIntConstant(try self.getScalarType(input_kind), input_kind, 0);
 748             try self.builder.emit(&self.builder.functions, SpirvOp.INotEqual, &.{
 749                 bool_type_id,
 750                 result_id,
 751                 input_id,
 752                 zero,
 753             });
 754         }
 755         try self.bindValue(result, result_id);
 756         return;
 757     }
 758 
 759     const opcode: u16 = if (isInt(input_kind) and isInt(result_kind)) blk: {
 760         if (bitWidth(input_kind) == bitWidth(result_kind)) {
 761             break :blk SpirvOp.Bitcast;
 762         }
 763         break :blk if (isSignedInt(input_kind)) SpirvOp.SConvert else SpirvOp.UConvert;
 764     } else if (isFloat(input_kind) and isFloat(result_kind)) blk: {
 765         break :blk SpirvOp.FConvert;
 766     } else if (isInt(input_kind) and isFloat(result_kind)) blk: {
 767         break :blk if (isSignedInt(input_kind)) SpirvOp.ConvertSToF else SpirvOp.ConvertUToF;
 768     } else if (isFloat(input_kind) and isInt(result_kind)) blk: {
 769         break :blk if (isSignedInt(result_kind)) SpirvOp.ConvertFToS else SpirvOp.ConvertFToU;
 770     } else {
 771         return error.UnsupportedType;
 772     };
 773 
 774     const result_id = self.builder.newId();
 775     try self.builder.emit(&self.builder.functions, opcode, &.{
 776         result_type_id,
 777         result_id,
 778         input_id,
 779     });
 780     try self.bindValue(result, result_id);
 781 }
 782 
 783 pub fn emitBitcast(self: anytype, op: *ir.Operation) !void {
 784     if (op.operands.items.len != 1) return error.UnsupportedOperation;
 785     const input = op.operands.items[0].value;
 786     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 787 
 788     const input_kind = kindFromType(input.type) orelse return error.UnsupportedType;
 789     const result_kind = kindFromType(result.type) orelse return error.UnsupportedType;
 790 
 791     if (input_kind == .bool or result_kind == .bool) return error.UnsupportedType;
 792 
 793     if (bitWidth(input_kind) != bitWidth(result_kind)) return error.UnsupportedType;
 794 
 795     const input_id = try self.getValue(input);
 796 
 797     if (input_kind == result_kind) {
 798         try self.bindValue(result, input_id);
 799         return;
 800     }
 801 
 802     const result_type_id = try self.getTypeForValue(result);
 803     const result_id = self.builder.newId();
 804     try self.builder.emit(&self.builder.functions, SpirvOp.Bitcast, &.{
 805         result_type_id,
 806         result_id,
 807         input_id,
 808     });
 809     try self.bindValue(result, result_id);
 810 }
 811 
 812 pub fn emitCmp(self: anytype, op: *ir.Operation) !void {
 813     if (op.operands.items.len != 2) return error.UnsupportedOperation;
 814     const lhs = op.operands.items[0].value;
 815     const rhs = op.operands.items[1].value;
 816     const result = op.getResult(0) orelse return error.UnsupportedOperation;
 817 
 818     const cmp = ArithDialect.CmpOp{ .op = op };
 819     const pred = cmp.getPredicate() orelse return error.MissingAttribute;
 820 
 821     const kind = kindFromType(lhs.type) orelse return error.UnsupportedType;
 822     const lhs_id = try self.getValue(lhs);
 823     const rhs_id = try self.getValue(rhs);
 824     const result_type_id = try self.getTypeForValue(result);
 825 
 826     const opcode: u16 = if (isFloat(kind)) switch (pred) {
 827         .eq => SpirvOp.FOrdEqual,
 828         .ne => SpirvOp.FOrdNotEqual,
 829         .lt, .slt, .ult => SpirvOp.FOrdLessThan,
 830         .le, .sle, .ule => SpirvOp.FOrdLessThanEqual,
 831         .gt, .sgt, .ugt => SpirvOp.FOrdGreaterThan,
 832         .ge, .sge, .uge => SpirvOp.FOrdGreaterThanEqual,
 833     } else switch (pred) {
 834         .eq => SpirvOp.IEqual,
 835         .ne => SpirvOp.INotEqual,
 836         .lt => if (isSignedInt(kind)) SpirvOp.SLessThan else SpirvOp.ULessThan,
 837         .le => if (isSignedInt(kind)) SpirvOp.SLessThanEqual else SpirvOp.ULessThanEqual,
 838         .gt => if (isSignedInt(kind)) SpirvOp.SGreaterThan else SpirvOp.UGreaterThan,
 839         .ge => if (isSignedInt(kind)) SpirvOp.SGreaterThanEqual else SpirvOp.UGreaterThanEqual,
 840         .slt => SpirvOp.SLessThan,
 841         .sle => SpirvOp.SLessThanEqual,
 842         .sgt => SpirvOp.SGreaterThan,
 843         .sge => SpirvOp.SGreaterThanEqual,
 844         .ult => SpirvOp.ULessThan,
 845         .ule => SpirvOp.ULessThanEqual,
 846         .ugt => SpirvOp.UGreaterThan,
 847         .uge => SpirvOp.UGreaterThanEqual,
 848     };
 849 
 850     const result_id = self.builder.newId();
 851     try self.builder.emit(&self.builder.functions, opcode, &.{
 852         result_type_id,
 853         result_id,
 854         lhs_id,
 855         rhs_id,
 856     });
 857     try self.bindValue(result, result_id);
 858 }
 859 
 860 pub fn emitSelect(self: anytype, op: *ir.Operation) !void {
 861     const select = ArithDialect.SelectOp{ .op = op };
 862     const cond_id = try self.getValue(select.getCondition());
 863     const true_id = try self.getValue(select.getTrueValue());
 864     const false_id = try self.getValue(select.getFalseValue());
 865     const result = select.getResult();
 866     const result_type_id = try self.getTypeForValue(result);
 867 
 868     const result_id = self.builder.newId();
 869     try self.builder.emit(&self.builder.functions, SpirvOp.Select, &.{
 870         result_type_id,
 871         result_id,
 872         cond_id,
 873         true_id,
 874         false_id,
 875     });
 876     try self.bindValue(result, result_id);
 877 }
 878 
 879 pub fn elementByteSize(kind: Kind) ?usize {
 880     return switch (kind) {
 881         .i8, .u8 => 1,
 882         .i16, .u16, .f16 => 2,
 883         .i32, .u32, .f32 => 4,
 884         .i64, .u64, .f64 => 8,
 885         .bool => 1,
 886         .void => null,
 887     };
 888 }
 889 
 890 pub fn isFloat(kind: Kind) bool {
 891     return switch (kind) {
 892         .f16, .f32, .f64 => true,
 893         else => false,
 894     };
 895 }
 896 
 897 pub fn isSignedInt(kind: Kind) bool {
 898     return switch (kind) {
 899         .i8, .i16, .i32, .i64 => true,
 900         else => false,
 901     };
 902 }
 903 
 904 pub fn isUnsignedInt(kind: Kind) bool {
 905     return switch (kind) {
 906         .u8, .u16, .u32, .u64 => true,
 907         else => false,
 908     };
 909 }
 910 
 911 pub fn isInt(kind: Kind) bool {
 912     return isSignedInt(kind) or isUnsignedInt(kind);
 913 }
 914 
 915 pub fn bitWidth(kind: Kind) u32 {
 916     return switch (kind) {
 917         .i8, .u8 => 8,
 918         .i16, .u16, .f16 => 16,
 919         .i32, .u32, .f32 => 32,
 920         .i64, .u64, .f64 => 64,
 921         .bool => 1,
 922         .void => 0,
 923     };
 924 }
 925 
 926 pub fn isFullMaskConstant(kind: Kind, value: i64) bool {
 927     if (!isInt(kind)) return false;
 928     const bits = bitWidth(kind);
 929     if (bits == 0 or bits > 64) return false;
 930     const raw: u64 = @bitCast(value);
 931     if (bits == 64) return raw == ~@as(u64, 0);
 932     const shift: u6 = @intCast(bits);
 933     const full_mask: u64 = (@as(u64, 1) << shift) - 1;
 934     return (raw & full_mask) == full_mask;
 935 }
 936 
 937 pub const IntBits = struct {
 938     word0: u32,
 939     word1: u32,
 940     word_count: u32,
 941 };
 942 
 943 pub fn integerConstantBits(kind: Kind, value: i64) ?IntBits {
 944     switch (kind) {
 945         .i8 => {
 946             const v = std.math.cast(i8, value) orelse return null;
 947             const raw: u8 = @bitCast(v);
 948             return .{ .word0 = @as(u32, raw), .word1 = 0, .word_count = 1 };
 949         },
 950         .i16 => {
 951             const v = std.math.cast(i16, value) orelse return null;
 952             const raw: u16 = @bitCast(v);
 953             return .{ .word0 = @as(u32, raw), .word1 = 0, .word_count = 1 };
 954         },
 955         .i32 => {
 956             const v = std.math.cast(i32, value) orelse return null;
 957             return .{ .word0 = @bitCast(v), .word1 = 0, .word_count = 1 };
 958         },
 959         .u8 => {
 960             if (value < 0) return null;
 961             const v = std.math.cast(u8, @as(u64, @intCast(value))) orelse return null;
 962             return .{ .word0 = v, .word1 = 0, .word_count = 1 };
 963         },
 964         .u16 => {
 965             if (value < 0) return null;
 966             const v = std.math.cast(u16, @as(u64, @intCast(value))) orelse return null;
 967             return .{ .word0 = v, .word1 = 0, .word_count = 1 };
 968         },
 969         .u32 => {
 970             if (value < 0) return null;
 971             const v = std.math.cast(u32, @as(u64, @intCast(value))) orelse return null;
 972             return .{ .word0 = v, .word1 = 0, .word_count = 1 };
 973         },
 974         .i64 => {
 975             const v = value;
 976             const bits: u64 = @bitCast(v);
 977             return .{ .word0 = @intCast(bits & 0xffffffff), .word1 = @intCast(bits >> 32), .word_count = 2 };
 978         },
 979         .u64 => {
 980             if (value < 0) return null;
 981             const v: u64 = @intCast(value);
 982             return .{ .word0 = @intCast(v & 0xffffffff), .word1 = @intCast(v >> 32), .word_count = 2 };
 983         },
 984         else => return null,
 985     }
 986 }
 987 
 988 pub const FloatBits = struct {
 989     word0: u32,
 990     word1: u32,
 991     word_count: u32,
 992 };
 993 
 994 pub fn floatConstantBits(kind: Kind, value: f64) ?FloatBits {
 995     switch (kind) {
 996         .f16 => {
 997             const bits: u16 = @bitCast(@as(f16, @floatCast(value)));
 998             return .{ .word0 = @as(u32, bits), .word1 = 0, .word_count = 1 };
 999         },
1000         .f32 => {
1001             const bits: u32 = @bitCast(@as(f32, @floatCast(value)));
1002             return .{ .word0 = bits, .word1 = 0, .word_count = 1 };
1003         },
1004         .f64 => {
1005             const bits: u64 = @bitCast(value);
1006             return .{ .word0 = @intCast(bits & 0xffffffff), .word1 = @intCast(bits >> 32), .word_count = 2 };
1007         },
1008         else => return null,
1009     }
1010 }
1011 
1012 const BinaryIdsRecorder = struct {
1013     builder: ModuleBuilder,
1014     scalar_type_id: u32,
1015 
1016     fn init(allocator: std.mem.Allocator) BinaryIdsRecorder {
1017         return .{
1018             .builder = ModuleBuilder.init(allocator),
1019             .scalar_type_id = 99,
1020         };
1021     }
1022 
1023     fn deinit(self: *BinaryIdsRecorder) void {
1024         self.builder.deinit();
1025     }
1026 
1027     pub fn getScalarType(self: *BinaryIdsRecorder, kind: Kind) !u32 {
1028         try std.testing.expectEqual(Kind.f32, kind);
1029         return self.scalar_type_id;
1030     }
1031 };
1032 
1033 test "spirv scalar owner emits float binary ids" {
1034     var recorder = BinaryIdsRecorder.init(std.testing.allocator);
1035     defer recorder.deinit();
1036 
1037     const result_id = try emitBinaryArithIds(&recorder, .f32, 11, 12, .add);
1038 
1039     try std.testing.expectEqual(@as(u32, 1), result_id);
1040     try std.testing.expectEqual(@as(usize, 5), recorder.builder.functions.items.len);
1041     try std.testing.expectEqual((@as(u32, 5) << 16) | @as(u32, SpirvOp.FAdd), recorder.builder.functions.items[0]);
1042     try std.testing.expectEqual(@as(u32, 99), recorder.builder.functions.items[1]);
1043     try std.testing.expectEqual(@as(u32, 1), recorder.builder.functions.items[2]);
1044     try std.testing.expectEqual(@as(u32, 11), recorder.builder.functions.items[3]);
1045     try std.testing.expectEqual(@as(u32, 12), recorder.builder.functions.items[4]);
1046 }
1047 
1048 test "spirv scalar owner classifies scalar kinds" {
1049     try std.testing.expectEqual(Kind.f32, kindFromName(arith.type_names.float32).?);
1050     try std.testing.expect(isFloat(.f64));
1051     try std.testing.expect(isSignedInt(.i32));
1052     try std.testing.expect(isUnsignedInt(.u32));
1053     try std.testing.expectEqual(@as(u32, 64), bitWidth(.f64));
1054 }