authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-09-16 13:16:02+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-09-23 12:36:44-07:00
log66036e600058081ce2f9248fd221900dce793cdd
tree33ffcc5554934f9e9eafe873d5f39f50df017dbe
parentb30cd679878ab0fab7f1e1589c348a2477d542aa

spirv: remove indirect constant lowering

It is stupid and I hate it.

1 files changed, 3 insertions(+), 572 deletions(-)

src/codegen/spirv.zig+3-572
...@@ -520,576 +520,6 @@ pub const DeclGen = struct {...@@ -520,576 +520,6 @@ pub const DeclGen = struct {
520 }520 }
521 }521 }
522522
523 const IndirectConstantLowering = struct {
524 const undef = 0xAA;
525
526 dg: *DeclGen,
527 /// Cached reference of the u32 type.
528 u32_ty_ref: CacheRef,
529 /// The members of the resulting structure type
530 members: std.ArrayList(CacheRef),
531 /// The initializers of each of the members.
532 initializers: std.ArrayList(IdRef),
533 /// The current size of the structure. Includes
534 /// the bytes in partial_word.
535 size: u32 = 0,
536 /// The partially filled last constant.
537 /// If full, its flushed.
538 partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{},
539 /// The declaration dependencies of the constant we are lowering.
540 decl_deps: std.AutoArrayHashMap(SpvModule.Decl.Index, void),
541
542 /// Utility function to get the section that instructions should be lowered to.
543 fn section(self: *@This()) *SpvSection {
544 return &self.dg.spv.globals.section;
545 }
546
547 /// Flush the partial_word to the members. If the partial_word is not
548 /// filled, this adds padding bytes (which are undefined).
549 fn flush(self: *@This()) !void {
550 if (self.partial_word.len == 0) {
551 // No need to add it there.
552 return;
553 }
554
555 for (self.partial_word.unusedCapacitySlice()) |*unused| {
556 // TODO: Perhaps we should generate OpUndef for these bytes?
557 unused.* = undef;
558 }
559
560 const word = @as(Word, @bitCast(self.partial_word.buffer));
561 const result_id = try self.dg.spv.constInt(self.u32_ty_ref, word);
562 try self.members.append(self.u32_ty_ref);
563 try self.initializers.append(result_id);
564
565 self.partial_word.len = 0;
566 self.size = std.mem.alignForward(u32, self.size, @sizeOf(Word));
567 }
568
569 /// Fill the buffer with undefined values until the size is aligned to `align`.
570 fn fillToAlign(self: *@This(), alignment: u32) !void {
571 const target_size = std.mem.alignForward(u32, self.size, alignment);
572 try self.addUndef(target_size - self.size);
573 }
574
575 fn addUndef(self: *@This(), amt: u64) !void {
576 for (0..@as(usize, @intCast(amt))) |_| {
577 try self.addByte(undef);
578 }
579 }
580
581 /// Add a single byte of data to the constant.
582 fn addByte(self: *@This(), data: u8) !void {
583 self.partial_word.append(data) catch {
584 try self.flush();
585 self.partial_word.append(data) catch unreachable;
586 };
587 self.size += 1;
588 }
589
590 /// Add many bytes of data to the constnat.
591 fn addBytes(self: *@This(), data: []const u8) !void {
592 // TODO: Improve performance by adding in bulk, or something?
593 for (data) |byte| {
594 try self.addByte(byte);
595 }
596 }
597
598 fn addPtr(self: *@This(), ptr_ty_ref: CacheRef, ptr_id: IdRef) !void {
599 // TODO: Double check pointer sizes here.
600 // shared pointers might be u32...
601 const target = self.dg.getTarget();
602 const width = @divExact(target.ptrBitWidth(), 8);
603 if (self.size % width != 0) {
604 return self.dg.todo("misaligned pointer constants", .{});
605 }
606 try self.members.append(ptr_ty_ref);
607 try self.initializers.append(ptr_id);
608 self.size += width;
609 }
610
611 fn addNullPtr(self: *@This(), ptr_ty_ref: CacheRef) !void {
612 const result_id = try self.dg.spv.constNull(ptr_ty_ref);
613 try self.addPtr(ptr_ty_ref, result_id);
614 }
615
616 fn addConstInt(self: *@This(), comptime T: type, value: T) !void {
617 if (@bitSizeOf(T) % 8 != 0) {
618 @compileError("todo: non byte aligned int constants");
619 }
620
621 // TODO: Swap endianness if the compiler is big endian.
622 try self.addBytes(std.mem.asBytes(&value));
623 }
624
625 fn addConstBool(self: *@This(), value: bool) !void {
626 try self.addByte(@intFromBool(value)); // TODO: Keep in sync with something?
627 }
628
629 fn addInt(self: *@This(), ty: Type, val: Value) !void {
630 const mod = self.dg.module;
631 const len = ty.abiSize(mod);
632 if (val.isUndef(mod)) {
633 try self.addUndef(len);
634 return;
635 }
636
637 const int_info = ty.intInfo(mod);
638 const int_bits = switch (int_info.signedness) {
639 .signed => @as(u64, @bitCast(val.toSignedInt(mod))),
640 .unsigned => val.toUnsignedInt(mod),
641 };
642
643 // TODO: Swap endianess if the compiler is big endian.
644 try self.addBytes(std.mem.asBytes(&int_bits)[0..@as(usize, @intCast(len))]);
645 }
646
647 fn addFloat(self: *@This(), ty: Type, val: Value) !void {
648 const mod = self.dg.module;
649 const target = self.dg.getTarget();
650 const len = ty.abiSize(mod);
651
652 // TODO: Swap endianess if the compiler is big endian.
653 switch (ty.floatBits(target)) {
654 16 => {
655 const float_bits = val.toFloat(f16, mod);
656 try self.addBytes(std.mem.asBytes(&float_bits)[0..@as(usize, @intCast(len))]);
657 },
658 32 => {
659 const float_bits = val.toFloat(f32, mod);
660 try self.addBytes(std.mem.asBytes(&float_bits)[0..@as(usize, @intCast(len))]);
661 },
662 64 => {
663 const float_bits = val.toFloat(f64, mod);
664 try self.addBytes(std.mem.asBytes(&float_bits)[0..@as(usize, @intCast(len))]);
665 },
666 else => unreachable,
667 }
668 }
669
670 fn addDeclRef(self: *@This(), ty: Type, decl_index: Decl.Index) !void {
671 const dg = self.dg;
672 const mod = dg.module;
673
674 const ty_ref = try self.dg.resolveType(ty, .indirect);
675 const ty_id = dg.typeId(ty_ref);
676
677 const decl = dg.module.declPtr(decl_index);
678 const spv_decl_index = try dg.resolveDecl(decl_index);
679
680 switch (mod.intern_pool.indexToKey(decl.val.ip_index)) {
681 .func => {
682 // TODO: Properly lower function pointers. For now we are going to hack around it and
683 // just generate an empty pointer. Function pointers are represented by usize for now,
684 // though.
685 try self.addInt(Type.usize, Value.zero_usize);
686 // TODO: Add dependency
687 return;
688 },
689 .extern_func => unreachable, // TODO
690 else => {
691 const result_id = dg.spv.allocId();
692
693 try self.decl_deps.put(spv_decl_index, {});
694
695 const decl_id = dg.spv.declPtr(spv_decl_index).result_id;
696 // TODO: Do we need a storage class cast here?
697 // TODO: We can probably eliminate these casts
698 try dg.spv.globals.section.emitSpecConstantOp(dg.spv.gpa, .OpBitcast, .{
699 .id_result_type = ty_id,
700 .id_result = result_id,
701 .operand = decl_id,
702 });
703
704 try self.addPtr(ty_ref, result_id);
705 },
706 }
707 }
708
709 fn lower(self: *@This(), ty: Type, arg_val: Value) !void {
710 const dg = self.dg;
711 const mod = dg.module;
712 const ip = &mod.intern_pool;
713
714 var val = arg_val;
715 switch (ip.indexToKey(val.toIntern())) {
716 .runtime_value => |rt| val = rt.val.toValue(),
717 else => {},
718 }
719
720 if (val.isUndefDeep(mod)) {
721 const size = ty.abiSize(mod);
722 return try self.addUndef(size);
723 }
724
725 switch (ip.indexToKey(val.toIntern())) {
726 .int_type,
727 .ptr_type,
728 .array_type,
729 .vector_type,
730 .opt_type,
731 .anyframe_type,
732 .error_union_type,
733 .simple_type,
734 .struct_type,
735 .anon_struct_type,
736 .union_type,
737 .opaque_type,
738 .enum_type,
739 .func_type,
740 .error_set_type,
741 .inferred_error_set_type,
742 => unreachable, // types, not values
743
744 .undef, .runtime_value => unreachable, // handled above
745 .simple_value => |simple_value| switch (simple_value) {
746 .undefined,
747 .void,
748 .null,
749 .empty_struct,
750 .@"unreachable",
751 .generic_poison,
752 => unreachable, // non-runtime values
753 .false, .true => try self.addConstBool(val.toBool()),
754 },
755 .variable,
756 .extern_func,
757 .func,
758 .enum_literal,
759 .empty_enum_value,
760 => unreachable, // non-runtime values
761 .int => try self.addInt(ty, val),
762 .err => |err| {
763 const int = try mod.getErrorValue(err.name);
764 try self.addConstInt(u16, @as(u16, @intCast(int)));
765 },
766 .error_union => |error_union| {
767 const err_ty = switch (error_union.val) {
768 .err_name => ty.errorUnionSet(mod),
769 .payload => Type.err_int,
770 };
771 const err_val = switch (error_union.val) {
772 .err_name => |err_name| (try mod.intern(.{ .err = .{
773 .ty = ty.errorUnionSet(mod).toIntern(),
774 .name = err_name,
775 } })).toValue(),
776 .payload => try mod.intValue(Type.err_int, 0),
777 };
778 const payload_ty = ty.errorUnionPayload(mod);
779 const eu_layout = dg.errorUnionLayout(payload_ty);
780 if (!eu_layout.payload_has_bits) {
781 // We use the error type directly as the type.
782 try self.lower(err_ty, err_val);
783 return;
784 }
785
786 const payload_size = payload_ty.abiSize(mod);
787 const error_size = err_ty.abiSize(mod);
788 const ty_size = ty.abiSize(mod);
789 const padding = ty_size - payload_size - error_size;
790
791 const payload_val = switch (error_union.val) {
792 .err_name => try mod.intern(.{ .undef = payload_ty.toIntern() }),
793 .payload => |payload| payload,
794 }.toValue();
795
796 if (eu_layout.error_first) {
797 try self.lower(err_ty, err_val);
798 try self.lower(payload_ty, payload_val);
799 } else {
800 try self.lower(payload_ty, payload_val);
801 try self.lower(err_ty, err_val);
802 }
803
804 try self.addUndef(padding);
805 },
806 .enum_tag => {
807 const int_val = try val.intFromEnum(ty, mod);
808
809 const int_ty = ty.intTagType(mod);
810
811 try self.lower(int_ty, int_val);
812 },
813 .float => try self.addFloat(ty, val),
814 .ptr => |ptr| {
815 const ptr_ty = switch (ptr.len) {
816 .none => ty,
817 else => ty.slicePtrFieldType(mod),
818 };
819 switch (ptr.addr) {
820 .decl => |decl| try self.addDeclRef(ptr_ty, decl),
821 .mut_decl => |mut_decl| try self.addDeclRef(ptr_ty, mut_decl.decl),
822 .int => |int| try self.addInt(Type.usize, int.toValue()),
823 else => |tag| return dg.todo("pointer value of type {s}", .{@tagName(tag)}),
824 }
825 if (ptr.len != .none) {
826 try self.addInt(Type.usize, ptr.len.toValue());
827 }
828 },
829 .opt => {
830 const payload_ty = ty.optionalChild(mod);
831 const payload_val = val.optionalValue(mod);
832 const abi_size = ty.abiSize(mod);
833
834 if (!payload_ty.hasRuntimeBits(mod)) {
835 try self.addConstBool(payload_val != null);
836 return;
837 } else if (ty.optionalReprIsPayload(mod)) {
838 // Optional representation is a nullable pointer or slice.
839 if (payload_val) |pl_val| {
840 try self.lower(payload_ty, pl_val);
841 } else {
842 const ptr_ty_ref = try dg.resolveType(ty, .indirect);
843 try self.addNullPtr(ptr_ty_ref);
844 }
845 return;
846 }
847
848 // Optional representation is a structure.
849 // { Payload, Bool }
850
851 // Subtract 1 for @sizeOf(bool).
852 // TODO: Make this not hardcoded.
853 const payload_size = payload_ty.abiSize(mod);
854 const padding = abi_size - payload_size - 1;
855
856 if (payload_val) |pl_val| {
857 try self.lower(payload_ty, pl_val);
858 } else {
859 try self.addUndef(payload_size);
860 }
861 try self.addConstBool(payload_val != null);
862 try self.addUndef(padding);
863 },
864 .aggregate => |aggregate| switch (ip.indexToKey(ty.ip_index)) {
865 .array_type => |array_type| {
866 const elem_ty = array_type.child.toType();
867 switch (aggregate.storage) {
868 .bytes => |bytes| try self.addBytes(bytes),
869 .elems, .repeated_elem => {
870 for (0..@as(usize, @intCast(array_type.len))) |i| {
871 try self.lower(elem_ty, switch (aggregate.storage) {
872 .bytes => unreachable,
873 .elems => |elem_vals| elem_vals[@as(usize, @intCast(i))].toValue(),
874 .repeated_elem => |elem_val| elem_val.toValue(),
875 });
876 }
877 },
878 }
879 if (array_type.sentinel != .none) {
880 try self.lower(elem_ty, array_type.sentinel.toValue());
881 }
882 },
883 .vector_type => return dg.todo("indirect constant of type {}", .{ty.fmt(mod)}),
884 .struct_type => {
885 const struct_type = mod.typeToStruct(ty).?;
886 if (struct_type.layout == .Packed) {
887 return dg.todo("packed struct constants", .{});
888 }
889
890 // TODO iterate with runtime order instead so that struct field
891 // reordering can be enabled for this backend.
892 const struct_begin = self.size;
893 for (struct_type.field_types.get(ip), 0..) |field_ty, i_usize| {
894 const i: u32 = @intCast(i_usize);
895 if (struct_type.fieldIsComptime(ip, i)) continue;
896 if (!field_ty.toType().hasRuntimeBits(mod)) continue;
897
898 const field_val = switch (aggregate.storage) {
899 .bytes => |bytes| try ip.get(mod.gpa, .{ .int = .{
900 .ty = field_ty,
901 .storage = .{ .u64 = bytes[i] },
902 } }),
903 .elems => |elems| elems[i],
904 .repeated_elem => |elem| elem,
905 };
906 try self.lower(field_ty.toType(), field_val.toValue());
907
908 // Add padding if required.
909 // TODO: Add to type generation as well?
910 const unpadded_field_end = self.size - struct_begin;
911 const padded_field_end = ty.structFieldOffset(i + 1, mod);
912 const padding = padded_field_end - unpadded_field_end;
913 try self.addUndef(padding);
914 }
915 },
916 .anon_struct_type => unreachable, // TODO
917 else => unreachable,
918 },
919 .un => |un| {
920 const layout = ty.unionGetLayout(mod);
921
922 if (layout.payload_size == 0) {
923 return try self.lower(ty.unionTagTypeSafety(mod).?, un.tag.toValue());
924 }
925
926 const union_obj = mod.typeToUnion(ty).?;
927 if (union_obj.getLayout(ip) == .Packed) {
928 return dg.todo("packed union constants", .{});
929 }
930
931 const active_field = ty.unionTagFieldIndex(un.tag.toValue(), dg.module).?;
932 const active_field_ty = union_obj.field_types.get(ip)[active_field].toType();
933
934 const has_tag = layout.tag_size != 0;
935 const tag_first = layout.tag_align.compare(.gte, layout.payload_align);
936
937 if (has_tag and tag_first) {
938 try self.lower(ty.unionTagTypeSafety(mod).?, un.tag.toValue());
939 }
940
941 const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime(mod)) blk: {
942 try self.lower(active_field_ty, un.val.toValue());
943 break :blk active_field_ty.abiSize(mod);
944 } else 0;
945
946 const payload_padding_len = layout.payload_size - active_field_size;
947 try self.addUndef(payload_padding_len);
948
949 if (has_tag and !tag_first) {
950 try self.lower(ty.unionTagTypeSafety(mod).?, un.tag.toValue());
951 }
952
953 try self.addUndef(layout.padding);
954 },
955 .memoized_call => unreachable,
956 }
957 }
958 };
959
960 /// Returns a pointer to `val`. The value is placed directly
961 /// into the storage class `storage_class`, and this is also where the resulting
962 /// pointer points to. Note: result is not necessarily an OpVariable instruction!
963 fn lowerIndirectConstant(
964 self: *DeclGen,
965 spv_decl_index: SpvModule.Decl.Index,
966 ty: Type,
967 val: Value,
968 storage_class: StorageClass,
969 cast_to_generic: bool,
970 alignment: u32,
971 ) Error!void {
972 // To simplify constant generation, we're going to generate constants as a word-array, and
973 // pointer cast the result to the right type.
974 // This means that the final constant will be generated as follows:
975 // %T = OpTypeStruct %members...
976 // %P = OpTypePointer %T
977 // %U = OpTypePointer %ty
978 // %1 = OpConstantComposite %T %initializers...
979 // %2 = OpVariable %P %1
980 // %result_id = OpSpecConstantOp OpBitcast %U %2
981 //
982 // The members consist of two options:
983 // - Literal values: ints, strings, etc. These are generated as u32 words.
984 // - Relocations, such as pointers: These are generated by embedding the pointer into the
985 // to-be-generated structure. There are two options here, depending on the alignment of the
986 // pointer value itself (not the alignment of the pointee).
987 // - Natively or over-aligned values. These can just be generated directly.
988 // - Underaligned pointers. These need to be packed into the word array by using a mixture of
989 // OpSpecConstantOp instructions such as OpConvertPtrToU, OpBitcast, OpShift, etc.
990
991 // TODO: Implement alignment here.
992 // This is hoing to require some hacks because there is no real way to
993 // set an OpVariable's alignment.
994 _ = alignment;
995
996 assert(storage_class != .Generic and storage_class != .Function);
997
998 const var_id = self.spv.allocId();
999 log.debug("lowerIndirectConstant: id = {}, index = {}, ty = {}, val = {}", .{ var_id.id, @intFromEnum(spv_decl_index), ty.fmt(self.module), val.fmtDebug() });
1000
1001 const section = &self.spv.globals.section;
1002
1003 const ty_ref = try self.resolveType(ty, .indirect);
1004 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class);
1005
1006 // const target = self.getTarget();
1007
1008 // TODO: Fix the resulting global linking for these paths.
1009 // if (val.isUndef(mod)) {
1010 // // Special case: the entire value is undefined. In this case, we can just
1011 // // generate an OpVariable with no initializer.
1012 // return try section.emit(self.spv.gpa, .OpVariable, .{
1013 // .id_result_type = self.typeId(ptr_ty_ref),
1014 // .id_result = result_id,
1015 // .storage_class = storage_class,
1016 // });
1017 // } else if (ty.abiSize(mod) == 0) {
1018 // // Special case: if the type has no size, then return an undefined pointer.
1019 // return try section.emit(self.spv.gpa, .OpUndef, .{
1020 // .id_result_type = self.typeId(ptr_ty_ref),
1021 // .id_result = result_id,
1022 // });
1023 // }
1024
1025 // TODO: Capture the above stuff in here as well...
1026 const begin_inst = self.spv.beginGlobal();
1027
1028 const u32_ty_ref = try self.intType(.unsigned, 32);
1029 var icl = IndirectConstantLowering{
1030 .dg = self,
1031 .u32_ty_ref = u32_ty_ref,
1032 .members = std.ArrayList(CacheRef).init(self.gpa),
1033 .initializers = std.ArrayList(IdRef).init(self.gpa),
1034 .decl_deps = std.AutoArrayHashMap(SpvModule.Decl.Index, void).init(self.gpa),
1035 };
1036
1037 defer icl.members.deinit();
1038 defer icl.initializers.deinit();
1039 defer icl.decl_deps.deinit();
1040
1041 try icl.lower(ty, val);
1042 try icl.flush();
1043
1044 const constant_struct_ty_ref = try self.spv.resolve(.{ .struct_type = .{
1045 .member_types = icl.members.items,
1046 } });
1047 const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class);
1048
1049 const constant_struct_id = self.spv.allocId();
1050 try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{
1051 .id_result_type = self.typeId(constant_struct_ty_ref),
1052 .id_result = constant_struct_id,
1053 .constituents = icl.initializers.items,
1054 });
1055
1056 self.spv.globalPtr(spv_decl_index).?.result_id = var_id;
1057 try section.emit(self.spv.gpa, .OpVariable, .{
1058 .id_result_type = self.typeId(ptr_constant_struct_ty_ref),
1059 .id_result = var_id,
1060 .storage_class = storage_class,
1061 .initializer = constant_struct_id,
1062 });
1063 // TODO: Set alignment of OpVariable.
1064 // TODO: We may be able to eliminate these casts.
1065
1066 const const_ptr_id = try self.makePointerConstant(section, ptr_constant_struct_ty_ref, var_id);
1067 const result_id = self.spv.declPtr(spv_decl_index).result_id;
1068
1069 const bitcast_result_id = if (cast_to_generic)
1070 self.spv.allocId()
1071 else
1072 result_id;
1073
1074 try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{
1075 .id_result_type = self.typeId(ptr_ty_ref),
1076 .id_result = bitcast_result_id,
1077 .operand = const_ptr_id,
1078 });
1079
1080 if (cast_to_generic) {
1081 const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic);
1082 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
1083 .id_result_type = self.typeId(generic_ptr_ty_ref),
1084 .id_result = result_id,
1085 .pointer = bitcast_result_id,
1086 });
1087 }
1088
1089 try self.spv.declareDeclDeps(spv_decl_index, icl.decl_deps.keys());
1090 self.spv.endGlobal(spv_decl_index, begin_inst);
1091 }
1092
1093 /// This function generates a load for a constant in direct (ie, non-memory) representation.523 /// This function generates a load for a constant in direct (ie, non-memory) representation.
1094 /// When the constant is simple, it can be generated directly using OpConstant instructions.524 /// When the constant is simple, it can be generated directly using OpConstant instructions.
1095 /// When the constant is more complicated however, it needs to be constructed using multiple values. This525 /// When the constant is more complicated however, it needs to be constructed using multiple values. This
...@@ -2104,8 +1534,6 @@ pub const DeclGen = struct {...@@ -2104,8 +1534,6 @@ pub const DeclGen = struct {
2104 .id_result = decl_id,1534 .id_result = decl_id,
2105 .storage_class = actual_storage_class,1535 .storage_class = actual_storage_class,
2106 });1536 });
2107 // TODO: We should be able to get rid of this by now...
2108 self.spv.endGlobal(spv_decl_index, begin);
21091537
2110 // Now emit the instructions that initialize the variable.1538 // Now emit the instructions that initialize the variable.
2111 const initializer_id = self.spv.allocId();1539 const initializer_id = self.spv.allocId();
...@@ -2127,6 +1555,9 @@ pub const DeclGen = struct {...@@ -2127,6 +1555,9 @@ pub const DeclGen = struct {
2127 .object = val_id,1555 .object = val_id,
2128 });1556 });
21291557
1558 // TODO: We should be able to get rid of this by now...
1559 self.spv.endGlobal(spv_decl_index, begin);
1560
2130 try self.func.body.emit(self.spv.gpa, .OpReturn, {});1561 try self.func.body.emit(self.spv.gpa, .OpReturn, {});
2131 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});1562 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
2132 try self.spv.addFunction(spv_decl_index, self.func);1563 try self.spv.addFunction(spv_decl_index, self.func);