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 }