authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-10-12 22:09:34+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-10-15 14:00:31+02:00
logf4064d98e23eaa4ad0ec76aca7a341d3e923e86e
tree76f0793dd7ba96b22cbf7110b6428bd3fba75cfd
parent10b8171466f6a2572cea0b8e70eba33be0020ac2
signaturebadge-check Signed by SSH key SHA256:CQ99aPxq+RueiL9u7z0FEki5Fm7V6T8q4PrEGmINrA4

spirv: optional comparison


1 files changed, 103 insertions(+), 52 deletions(-)

src/codegen/spirv.zig+103-52
...@@ -52,12 +52,6 @@ const Block = struct {...@@ -52,12 +52,6 @@ const Block = struct {
5252
53const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, *Block);53const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, *Block);
5454
55/// Maps Zig decl indices to SPIR-V linking information.
56pub const DeclLinkMap = std.AutoHashMapUnmanaged(Decl.Index, SpvModule.Decl.Index);
57
58/// Maps anon decl indices to SPIR-V linking information.
59pub const AnonDeclLinkMap = std.AutoHashMapUnmanaged(struct { InternPool.Index, StorageClass }, SpvModule.Decl.Index);
60
61/// This structure holds information that is relevant to the entire compilation,55/// This structure holds information that is relevant to the entire compilation,
62/// in contrast to `DeclGen`, which only holds relevant information about a56/// in contrast to `DeclGen`, which only holds relevant information about a
63/// single decl.57/// single decl.
...@@ -70,10 +64,10 @@ pub const Object = struct {...@@ -70,10 +64,10 @@ pub const Object = struct {
7064
71 /// The Zig module that this object file is generated for.65 /// The Zig module that this object file is generated for.
72 /// A map of Zig decl indices to SPIR-V decl indices.66 /// A map of Zig decl indices to SPIR-V decl indices.
73 decl_link: DeclLinkMap = .{},67 decl_link: std.AutoHashMapUnmanaged(Decl.Index, SpvModule.Decl.Index) = .{},
7468
75 /// A map of Zig InternPool indices for anonymous decls to SPIR-V decl indices.69 /// A map of Zig InternPool indices for anonymous decls to SPIR-V decl indices.
76 anon_decl_link: AnonDeclLinkMap = .{},70 anon_decl_link: std.AutoHashMapUnmanaged(struct { InternPool.Index, StorageClass }, SpvModule.Decl.Index) = .{},
7771
78 /// A map that maps AIR intern pool indices to SPIR-V cache references (which72 /// A map that maps AIR intern pool indices to SPIR-V cache references (which
79 /// is basically the same thing except for SPIR-V).73 /// is basically the same thing except for SPIR-V).
...@@ -1266,22 +1260,32 @@ const DeclGen = struct {...@@ -1266,22 +1260,32 @@ const DeclGen = struct {
12661260
1267 const elem_ty = ty.childType(mod);1261 const elem_ty = ty.childType(mod);
1268 const elem_ty_ref = try self.resolveType(elem_ty, .indirect);1262 const elem_ty_ref = try self.resolveType(elem_ty, .indirect);
1269 const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel(mod)) orelse {1263 var total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel(mod)) orelse {
1270 return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel(mod)});1264 return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel(mod)});
1271 };1265 };
1272 if (!ty.hasRuntimeBitsIgnoreComptime(mod)) {1266 const ty_ref = if (!elem_ty.hasRuntimeBitsIgnoreComptime(mod)) blk: {
1273 // The size of the array would be 0, but that is not allowed in SPIR-V.1267 // The size of the array would be 0, but that is not allowed in SPIR-V.
1274 // This path can be reached for example when there is a slicing of a pointer1268 // This path can be reached when the backend is asked to generate a pointer to
1275 // that produces a zero-length array. In all cases where this type can be generated,1269 // an array of some zero-bit type. This should always be an indirect path.
1276 // we should be in an indirect path (direct uses of this type should be filtered out in Sema).
1277 assert(repr == .indirect);1270 assert(repr == .indirect);
12781271
1279 return try self.spv.resolve(.{ .opaque_type = .{1272 // We cannot use the child type here, so just use an opaque type.
1273 break :blk try self.spv.resolve(.{ .opaque_type = .{
1280 .name = try self.spv.resolveString("zero-sized array"),1274 .name = try self.spv.resolveString("zero-sized array"),
1281 } });1275 } });
1282 }1276 } else if (total_len == 0) blk: {
1277 // The size of the array would be 0, but that is not allowed in SPIR-V.
1278 // This path can be reached for example when there is a slicing of a pointer
1279 // that produces a zero-length array. In all cases where this type can be generated,
1280 // this should be an indirect path.
1281 assert(repr == .indirect);
1282
1283 // In this case, we have an array of a non-zero sized type. In this case,
1284 // generate an array of 1 element instead, so that ptr_elem_ptr instructions
1285 // can be lowered to ptrAccessChain instead of manually performing the math.
1286 break :blk try self.spv.arrayType(1, elem_ty_ref);
1287 } else try self.spv.arrayType(total_len, elem_ty_ref);
12831288
1284 const ty_ref = try self.spv.arrayType(total_len, elem_ty_ref);
1285 try self.type_map.put(self.gpa, ty.toIntern(), .{ .ty_ref = ty_ref });1289 try self.type_map.put(self.gpa, ty.toIntern(), .{ .ty_ref = ty_ref });
1286 return ty_ref;1290 return ty_ref;
1287 },1291 },
...@@ -2554,38 +2558,85 @@ const DeclGen = struct {...@@ -2554,38 +2558,85 @@ const DeclGen = struct {
2554 var cmp_lhs_id = lhs_id;2558 var cmp_lhs_id = lhs_id;
2555 var cmp_rhs_id = rhs_id;2559 var cmp_rhs_id = rhs_id;
2556 const bool_ty_ref = try self.resolveType(Type.bool, .direct);2560 const bool_ty_ref = try self.resolveType(Type.bool, .direct);
2557 const opcode: Opcode = opcode: {2561 const op_ty = switch (ty.zigTypeTag(mod)) {
2558 const op_ty = switch (ty.zigTypeTag(mod)) {2562 .Int, .Bool, .Float => ty,
2559 .Int, .Bool, .Float => ty,2563 .Enum => ty.intTagType(mod),
2560 .Enum => ty.intTagType(mod),2564 .ErrorSet => Type.u16,
2561 .ErrorSet => Type.u16,2565 .Pointer => blk: {
2562 .Pointer => blk: {2566 // Note that while SPIR-V offers OpPtrEqual and OpPtrNotEqual, they are
2563 // Note that while SPIR-V offers OpPtrEqual and OpPtrNotEqual, they are2567 // currently not implemented in the SPIR-V LLVM translator. Thus, we emit these using
2564 // currently not implemented in the SPIR-V LLVM translator. Thus, we emit these using2568 // OpConvertPtrToU...
2565 // OpConvertPtrToU...2569 cmp_lhs_id = self.spv.allocId();
2566 cmp_lhs_id = self.spv.allocId();2570 cmp_rhs_id = self.spv.allocId();
2567 cmp_rhs_id = self.spv.allocId();2571
25682572 const usize_ty_id = self.typeId(try self.sizeType());
2569 const usize_ty_id = self.typeId(try self.sizeType());2573
25702574 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
2571 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{2575 .id_result_type = usize_ty_id,
2572 .id_result_type = usize_ty_id,2576 .id_result = cmp_lhs_id,
2573 .id_result = cmp_lhs_id,2577 .pointer = lhs_id,
2574 .pointer = lhs_id,2578 });
2575 });
25762579
2577 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{2580 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
2578 .id_result_type = usize_ty_id,2581 .id_result_type = usize_ty_id,
2579 .id_result = cmp_rhs_id,2582 .id_result = cmp_rhs_id,
2580 .pointer = rhs_id,2583 .pointer = rhs_id,
2581 });2584 });
25822585
2583 break :blk Type.usize;2586 break :blk Type.usize;
2584 },2587 },
2585 .Optional => unreachable, // TODO2588 .Optional => {
2586 else => unreachable,2589 const payload_ty = ty.optionalChild(mod);
2587 };2590 if (ty.optionalReprIsPayload(mod)) {
2591 assert(payload_ty.hasRuntimeBitsIgnoreComptime(mod));
2592 assert(!payload_ty.isSlice(mod));
2593 return self.cmp(op, payload_ty, lhs_id, rhs_id);
2594 }
2595
2596 const lhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(mod))
2597 try self.extractField(Type.bool, lhs_id, 1)
2598 else
2599 try self.convertToDirect(Type.bool, lhs_id);
25882600
2601 const rhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(mod))
2602 try self.extractField(Type.bool, rhs_id, 1)
2603 else
2604 try self.convertToDirect(Type.bool, rhs_id);
2605
2606 const valid_cmp_id = try self.cmp(op, Type.bool, lhs_valid_id, rhs_valid_id);
2607 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
2608 return valid_cmp_id;
2609 }
2610
2611 // TODO: Should we short circuit here? It shouldn't affect correctness, but
2612 // perhaps it will generate more efficient code.
2613
2614 const lhs_pl_id = try self.extractField(payload_ty, lhs_id, 0);
2615 const rhs_pl_id = try self.extractField(payload_ty, rhs_id, 0);
2616
2617 const pl_cmp_id = try self.cmp(op, payload_ty, lhs_pl_id, rhs_pl_id);
2618
2619 // op == .eq => lhs_valid == rhs_valid && lhs_pl == rhs_pl
2620 // op == .neq => lhs_valid != rhs_valid || lhs_pl != rhs_pl
2621
2622 const result_id = self.spv.allocId();
2623 const args = .{
2624 .id_result_type = self.typeId(bool_ty_ref),
2625 .id_result = result_id,
2626 .operand_1 = valid_cmp_id,
2627 .operand_2 = pl_cmp_id,
2628 };
2629 switch (op) {
2630 .eq => try self.func.body.emit(self.spv.gpa, .OpLogicalAnd, args),
2631 .neq => try self.func.body.emit(self.spv.gpa, .OpLogicalOr, args),
2632 else => unreachable,
2633 }
2634 return result_id;
2635 },
2636 else => unreachable,
2637 };
2638
2639 const opcode: Opcode = opcode: {
2589 const info = try self.arithmeticTypeInfo(op_ty);2640 const info = try self.arithmeticTypeInfo(op_ty);
2590 const signedness = switch (info.class) {2641 const signedness = switch (info.class) {
2591 .composite_integer => {2642 .composite_integer => {
...@@ -2653,7 +2704,6 @@ const DeclGen = struct {...@@ -2653,7 +2704,6 @@ const DeclGen = struct {
2653 const lhs_id = try self.resolve(bin_op.lhs);2704 const lhs_id = try self.resolve(bin_op.lhs);
2654 const rhs_id = try self.resolve(bin_op.rhs);2705 const rhs_id = try self.resolve(bin_op.rhs);
2655 const ty = self.typeOf(bin_op.lhs);2706 const ty = self.typeOf(bin_op.lhs);
2656 assert(ty.eql(self.typeOf(bin_op.rhs), self.module));
26572707
2658 return try self.cmp(op, ty, lhs_id, rhs_id);2708 return try self.cmp(op, ty, lhs_id, rhs_id);
2659 }2709 }
...@@ -3061,16 +3111,17 @@ const DeclGen = struct {...@@ -3061,16 +3111,17 @@ const DeclGen = struct {
3061 const mod = self.module;3111 const mod = self.module;
3062 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;3112 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
3063 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;3113 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
3064 const ptr_ty = self.typeOf(bin_op.lhs);3114 const src_ptr_ty = self.typeOf(bin_op.lhs);
3065 const elem_ty = ptr_ty.childType(mod);3115 const elem_ty = src_ptr_ty.childType(mod);
3116 const ptr_id = try self.resolve(bin_op.lhs);
3117
3066 if (!elem_ty.hasRuntimeBitsIgnoreComptime(mod)) {3118 if (!elem_ty.hasRuntimeBitsIgnoreComptime(mod)) {
3067 const ptr_ty_ref = try self.resolveType(ptr_ty, .direct);3119 const dst_ptr_ty = self.typeOfIndex(inst);
3068 return try self.spv.constUndef(ptr_ty_ref);3120 return try self.bitCast(dst_ptr_ty, src_ptr_ty, ptr_id);
3069 }3121 }
30703122
3071 const ptr_id = try self.resolve(bin_op.lhs);
3072 const index_id = try self.resolve(bin_op.rhs);3123 const index_id = try self.resolve(bin_op.rhs);
3073 return try self.ptrElemPtr(ptr_ty, ptr_id, index_id);3124 return try self.ptrElemPtr(src_ptr_ty, ptr_id, index_id);
3074 }3125 }
30753126
3076 fn airArrayElemVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {3127 fn airArrayElemVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {