authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-09 01:29:39+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-09 01:51:53+02:00
log8bbfbfc956af163434c734e196d5c2a77e77ff07
tree8ed95f240d2736a4d82e915ecc9269f15ba834e2
parent80b84355692606ac840584baa62aaafdd8ecd425
signaturelock-open Commit is signed but in an unrecognized format.

spirv: improve linking globals

SPIR-V globals must be emitted in order, so that any declaration precedes usage. Zig, however, generates globals in random order. To this end we keep for each global a list of dependencies and perform a topological sort when flushing the module.

3 files changed, 298 insertions(+), 127 deletions(-)

src/codegen/spirv.zig+167-121
...@@ -32,12 +32,28 @@ const IncomingBlock = struct {...@@ -32,12 +32,28 @@ const IncomingBlock = struct {
32 break_value_id: IdRef,32 break_value_id: IdRef,
33};33};
3434
35pub const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct {35const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
36 label_id: IdRef,36 label_id: IdRef,
37 incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),37 incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),
38});38});
3939
40pub const DeclMap = std.AutoHashMap(Module.Decl.Index, IdResult);40/// Linking information about a particular decl.
41/// The active field of this enum depends on the type of the corresponding decl.
42const DeclLink = union {
43 /// Linking information about a function.
44 /// Active when the decl is a function.
45 func: struct {
46 /// Result-id of the OpFunction instruction.
47 result_id: IdResult,
48 },
49 /// Linking information about a global. This index points into the
50 /// SPIR-V module's `globals` array.
51 /// Active when the decl is a variable.
52 global: SpvModule.Global.Index,
53};
54
55/// Maps Zig decl indices to linking SPIR-V linking information.
56pub const DeclLinkMap = std.AutoHashMap(Module.Decl.Index, DeclLink);
4157
42/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.58/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
43pub const DeclGen = struct {59pub const DeclGen = struct {
...@@ -61,8 +77,8 @@ pub const DeclGen = struct {...@@ -61,8 +77,8 @@ pub const DeclGen = struct {
61 /// Note: If the declaration is not a function, this value will be undefined!77 /// Note: If the declaration is not a function, this value will be undefined!
62 liveness: Liveness,78 liveness: Liveness,
6379
64 /// Maps Zig Decl indices to SPIR-V result indices.80 /// Maps Zig Decl indices to SPIR-V globals.
65 decl_ids: *DeclMap,81 decl_link: *DeclLinkMap,
6682
67 /// An array of function argument result-ids. Each index corresponds with the83 /// An array of function argument result-ids. Each index corresponds with the
68 /// function argument of the same index.84 /// function argument of the same index.
...@@ -152,7 +168,7 @@ pub const DeclGen = struct {...@@ -152,7 +168,7 @@ pub const DeclGen = struct {
152 allocator: Allocator,168 allocator: Allocator,
153 module: *Module,169 module: *Module,
154 spv: *SpvModule,170 spv: *SpvModule,
155 decl_ids: *DeclMap,171 decl_link: *DeclLinkMap,
156 ) DeclGen {172 ) DeclGen {
157 return .{173 return .{
158 .gpa = allocator,174 .gpa = allocator,
...@@ -161,7 +177,7 @@ pub const DeclGen = struct {...@@ -161,7 +177,7 @@ pub const DeclGen = struct {
161 .decl_index = undefined,177 .decl_index = undefined,
162 .air = undefined,178 .air = undefined,
163 .liveness = undefined,179 .liveness = undefined,
164 .decl_ids = decl_ids,180 .decl_link = decl_link,
165 .next_arg_index = undefined,181 .next_arg_index = undefined,
166 .current_block_label_id = undefined,182 .current_block_label_id = undefined,
167 .error_msg = undefined,183 .error_msg = undefined,
...@@ -235,7 +251,8 @@ pub const DeclGen = struct {...@@ -235,7 +251,8 @@ pub const DeclGen = struct {
235 .function => val.castTag(.function).?.data.owner_decl,251 .function => val.castTag(.function).?.data.owner_decl,
236 else => unreachable,252 else => unreachable,
237 };253 };
238 return try self.resolveDecl(fn_decl_index);254 const link = try self.resolveDecl(fn_decl_index);
255 return link.func.result_id;
239 }256 }
240257
241 return try self.constant(ty, val);258 return try self.constant(ty, val);
...@@ -246,17 +263,22 @@ pub const DeclGen = struct {...@@ -246,17 +263,22 @@ pub const DeclGen = struct {
246263
247 /// Fetch or allocate a result id for decl index. This function also marks the decl as alive.264 /// Fetch or allocate a result id for decl index. This function also marks the decl as alive.
248 /// Note: Function does not actually generate the decl.265 /// Note: Function does not actually generate the decl.
249 fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !IdResult {266 fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !DeclLink {
250 const decl = self.module.declPtr(decl_index);267 const decl = self.module.declPtr(decl_index);
251 self.module.markDeclAlive(decl);268 self.module.markDeclAlive(decl);
252269
253 const entry = try self.decl_ids.getOrPut(decl_index);270 const entry = try self.decl_link.getOrPut(decl_index);
254 if (entry.found_existing) {
255 return entry.value_ptr.*;
256 }
257 const result_id = self.spv.allocId();271 const result_id = self.spv.allocId();
258 entry.value_ptr.* = result_id;272
259 return result_id;273 if (!entry.found_existing) {
274 if (decl.val.castTag(.function)) |_| {
275 entry.value_ptr.* = .{.func = .{ .result_id = result_id }};
276 } else {
277 entry.value_ptr.* = .{ .global = try self.spv.allocGlobal() };
278 }
279 }
280
281 return entry.value_ptr.*;
260 }282 }
261283
262 /// Start a new SPIR-V block, Emits the label of the new block, and stores which284 /// Start a new SPIR-V block, Emits the label of the new block, and stores which
...@@ -363,7 +385,7 @@ pub const DeclGen = struct {...@@ -363,7 +385,7 @@ pub const DeclGen = struct {
363 // As of yet, there is no vector support in the self-hosted compiler.385 // As of yet, there is no vector support in the self-hosted compiler.
364 .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}),386 .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}),
365 // TODO: For which types is this the case?387 // TODO: For which types is this the case?
366 else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmtDebug()}),388 else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmt(self.module)}),
367 };389 };
368 }390 }
369391
...@@ -399,7 +421,7 @@ pub const DeclGen = struct {...@@ -399,7 +421,7 @@ pub const DeclGen = struct {
399 try self.spv.sections.types_globals_constants.emit(421 try self.spv.sections.types_globals_constants.emit(
400 self.spv.gpa,422 self.spv.gpa,
401 .OpUndef,423 .OpUndef,
402 .{ .id_result_type = self.typeId(ty_ref), .id_result = result_id },424 .{ .id_result_type = self.typeId(ty_ref), .id_result = result_id }
403 );425 );
404 return result_id;426 return result_id;
405 }427 }
...@@ -423,6 +445,11 @@ pub const DeclGen = struct {...@@ -423,6 +445,11 @@ pub const DeclGen = struct {
423 /// If full, its flushed.445 /// If full, its flushed.
424 partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{},446 partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{},
425447
448 /// Utility function to get the section that instructions should be lowered to.
449 fn section(self: *@This()) *SpvSection {
450 return &self.dg.spv.globals.section;
451 }
452
426 /// Flush the partial_word to the members. If the partial_word is not453 /// Flush the partial_word to the members. If the partial_word is not
427 /// filled, this adds padding bytes (which are undefined).454 /// filled, this adds padding bytes (which are undefined).
428 fn flush(self: *@This()) !void {455 fn flush(self: *@This()) !void {
...@@ -438,6 +465,7 @@ pub const DeclGen = struct {...@@ -438,6 +465,7 @@ pub const DeclGen = struct {
438465
439 const word = @bitCast(Word, self.partial_word.buffer);466 const word = @bitCast(Word, self.partial_word.buffer);
440 const result_id = self.dg.spv.allocId();467 const result_id = self.dg.spv.allocId();
468 // TODO: Integrate with caching mechanism
441 try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word });469 try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word });
442 try self.members.append(.{ .ty = self.u32_ty_ref });470 try self.members.append(.{ .ty = self.u32_ty_ref });
443 try self.initializers.append(result_id);471 try self.initializers.append(result_id);
...@@ -523,10 +551,52 @@ pub const DeclGen = struct {...@@ -523,10 +551,52 @@ pub const DeclGen = struct {
523 try self.addBytes(std.mem.asBytes(&int_bits)[0..@intCast(usize, len)]);551 try self.addBytes(std.mem.asBytes(&int_bits)[0..@intCast(usize, len)]);
524 }552 }
525553
554 fn addDeclRef(self: *@This(), ty: Type, decl_index: Decl.Index) !void {
555 const dg = self.dg;
556
557 const ty_ref = try self.dg.resolveType(ty, .indirect);
558 const ty_id = dg.typeId(ty_ref);
559
560 const decl = dg.module.declPtr(decl_index);
561 const link = try dg.resolveDecl(decl_index);
562
563 switch (decl.val.tag()) {
564 .function => {
565 // TODO: Properly lower function pointers. For now we are going to hack around it and
566 // just generate an empty pointer. Function pointers are represented by usize for now,
567 // though.
568 try self.addInt(Type.usize, Value.initTag(.zero));
569 return;
570 },
571 .extern_fn => unreachable, // TODO
572 else => {
573 const result_id = dg.spv.allocId();
574 log.debug("addDeclRef {s} = {}", .{ decl.name, result_id.id });
575
576 const global = dg.spv.globalPtr(link.global);
577 try dg.spv.addGlobalDependency(link.global);
578 // TODO: Do we need a storage class cast here?
579 // TODO: We can probably eliminate these casts
580 try dg.spv.globals.section.emitSpecConstantOp(dg.spv.gpa, .OpBitcast, .{
581 .id_result_type = ty_id,
582 .id_result = result_id,
583 .operand = global.result_id,
584 });
585
586 try self.addPtr(ty_ref, result_id);
587 },
588 }
589 }
590
526 fn lower(self: *@This(), ty: Type, val: Value) !void {591 fn lower(self: *@This(), ty: Type, val: Value) !void {
527 const target = self.dg.getTarget();592 const target = self.dg.getTarget();
528 const dg = self.dg;593 const dg = self.dg;
529594
595 if (val.isUndef()) {
596 const size = ty.abiSize(target);
597 return try self.addUndef(size);
598 }
599
530 switch (ty.zigTypeTag()) {600 switch (ty.zigTypeTag()) {
531 .Int => try self.addInt(ty, val),601 .Int => try self.addInt(ty, val),
532 .Bool => try self.addConstBool(val.toBool()),602 .Bool => try self.addConstBool(val.toBool()),
...@@ -558,22 +628,20 @@ pub const DeclGen = struct {...@@ -558,22 +628,20 @@ pub const DeclGen = struct {
558 try self.addByte(@intCast(u8, sentinel.toUnsignedInt(target)));628 try self.addByte(@intCast(u8, sentinel.toUnsignedInt(target)));
559 }629 }
560 },630 },
631 .bytes => {
632 const bytes = val.castTag(.bytes).?.data;
633 try self.addBytes(bytes);
634 },
561 else => |tag| return dg.todo("indirect array constant with tag {s}", .{@tagName(tag)}),635 else => |tag| return dg.todo("indirect array constant with tag {s}", .{@tagName(tag)}),
562 },636 },
563 .Pointer => switch (val.tag()) {637 .Pointer => switch (val.tag()) {
564 .decl_ref_mut => {638 .decl_ref_mut => {
565 const ptr_ty_ref = try dg.resolveType(ty, .indirect);
566 const ptr_id = dg.spv.allocId();
567 const decl_index = val.castTag(.decl_ref_mut).?.data.decl_index;639 const decl_index = val.castTag(.decl_ref_mut).?.data.decl_index;
568 try dg.genDeclRef(ptr_ty_ref, ptr_id, decl_index);640 try self.addDeclRef(ty, decl_index);
569 try self.addPtr(ptr_ty_ref, ptr_id);
570 },641 },
571 .decl_ref => {642 .decl_ref => {
572 const ptr_ty_ref = try dg.resolveType(ty, .indirect);
573 const ptr_id = dg.spv.allocId();
574 const decl_index = val.castTag(.decl_ref).?.data;643 const decl_index = val.castTag(.decl_ref).?.data;
575 try dg.genDeclRef(ptr_ty_ref, ptr_id, decl_index);644 try self.addDeclRef(ty, decl_index);
576 try self.addPtr(ptr_ty_ref, ptr_id);
577 },645 },
578 .slice => {646 .slice => {
579 const slice = val.castTag(.slice).?.data;647 const slice = val.castTag(.slice).?.data;
...@@ -730,22 +798,31 @@ pub const DeclGen = struct {...@@ -730,22 +798,31 @@ pub const DeclGen = struct {
730 // - Underaligned pointers. These need to be packed into the word array by using a mixture of798 // - Underaligned pointers. These need to be packed into the word array by using a mixture of
731 // OpSpecConstantOp instructions such as OpConvertPtrToU, OpBitcast, OpShift, etc.799 // OpSpecConstantOp instructions such as OpConvertPtrToU, OpBitcast, OpShift, etc.
732800
733 log.debug("lowerIndirectConstant: ty = {}, val = {}", .{ ty.fmtDebug(), val.fmtDebug() });801 assert(storage_class != .Generic and storage_class != .Function);
734802
735 const constant_section = &self.spv.sections.types_globals_constants;803 log.debug("lowerIndirectConstant: ty = {}, val = {}", .{ ty.fmt(self.module), val.fmtDebug() });
804
805 const section = &self.spv.globals.section;
736806
737 const ty_ref = try self.resolveType(ty, .indirect);807 const ty_ref = try self.resolveType(ty, .indirect);
738 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, alignment);808 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, alignment);
739809
810 const target = self.getTarget();
811
740 if (val.isUndef()) {812 if (val.isUndef()) {
741 // Special case: the entire value is undefined. In this case, we can just813 // Special case: the entire value is undefined. In this case, we can just
742 // generate an OpVariable with no initializer.814 // generate an OpVariable with no initializer.
743 try constant_section.emit(self.spv.gpa, .OpVariable, .{815 return try section.emit(self.spv.gpa, .OpVariable, .{
744 .id_result_type = self.typeId(ptr_ty_ref),816 .id_result_type = self.typeId(ptr_ty_ref),
745 .id_result = result_id,817 .id_result = result_id,
746 .storage_class = storage_class,818 .storage_class = storage_class,
747 });819 });
748 return;820 } else if (ty.abiSize(target) == 0) {
821 // Special case: if the type has no size, then return an undefined pointer.
822 return try section.emit(self.spv.gpa, .OpUndef, .{
823 .id_result_type = self.typeId(ptr_ty_ref),
824 .id_result = result_id,
825 });
749 }826 }
750827
751 const u32_ty_ref = try self.intType(.unsigned, 32);828 const u32_ty_ref = try self.intType(.unsigned, 32);
...@@ -757,62 +834,42 @@ pub const DeclGen = struct {...@@ -757,62 +834,42 @@ pub const DeclGen = struct {
757 .initializers = std.ArrayList(IdRef).init(self.gpa),834 .initializers = std.ArrayList(IdRef).init(self.gpa),
758 };835 };
759836
760 try icl.lower(ty, val);
761 try icl.flush();
762
763 defer icl.members.deinit();837 defer icl.members.deinit();
764 defer icl.initializers.deinit();838 defer icl.initializers.deinit();
765839
840 try icl.lower(ty, val);
841 try icl.flush();
842
766 const constant_struct_ty_ref = try self.spv.simpleStructType(icl.members.items);843 const constant_struct_ty_ref = try self.spv.simpleStructType(icl.members.items);
767 const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class, alignment);844 const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class, alignment);
768845
769 const constant_struct_id = self.spv.allocId();846 const constant_struct_id = self.spv.allocId();
770 try constant_section.emit(self.spv.gpa, .OpSpecConstantComposite, .{847 try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{
771 .id_result_type = self.typeId(constant_struct_ty_ref),848 .id_result_type = self.typeId(constant_struct_ty_ref),
772 .id_result = constant_struct_id,849 .id_result = constant_struct_id,
773 .constituents = icl.initializers.items,850 .constituents = icl.initializers.items,
774 });851 });
775852
776 const var_id = self.spv.allocId();853 const var_id = self.spv.allocId();
777 switch (storage_class) {854 try section.emit(self.spv.gpa, .OpVariable, .{
778 .Generic => unreachable,855 .id_result_type = self.typeId(ptr_constant_struct_ty_ref),
779 .Function => {856 .id_result = var_id,
780 try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{857 .storage_class = storage_class,
781 .id_result_type = self.typeId(ptr_constant_struct_ty_ref),858 .initializer = constant_struct_id,
782 .id_result = var_id,859 });
783 .storage_class = storage_class,860 // TODO: Set alignment of OpVariable.
784 .initializer = constant_struct_id,861 // TODO: We may be able to eliminate this cast.
785 });862 try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{
786 // TODO: Set alignment of OpVariable.863 .id_result_type = self.typeId(ptr_ty_ref),
787864 .id_result = result_id,
788 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{865 .operand = var_id,
789 .id_result_type = self.typeId(ptr_ty_ref),866 });
790 .id_result = result_id,
791 .operand = var_id,
792 });
793 },
794 else => {
795 try constant_section.emit(self.spv.gpa, .OpVariable, .{
796 .id_result_type = self.typeId(ptr_constant_struct_ty_ref),
797 .id_result = var_id,
798 .storage_class = storage_class,
799 .initializer = constant_struct_id,
800 });
801 // TODO: Set alignment of OpVariable.
802
803 try constant_section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{
804 .id_result_type = self.typeId(ptr_ty_ref),
805 .id_result = result_id,
806 .operand = var_id,
807 });
808 },
809 }
810 }867 }
811868
812 /// This function generates a load for a constant in direct (ie, non-memory) representation.869 /// This function generates a load for a constant in direct (ie, non-memory) representation.
813 /// When the constant is simple, it can be generated directly using OpConstant instructions. When870 /// When the constant is simple, it can be generated directly using OpConstant instructions. When
814 /// the constant is more complicated however, it needs to be lowered to an indirect constant, which871 /// the constant is more complicated however, it needs to be lowered to an indirect constant, which
815 /// is then loaded using OpLoad. Such values are loaded into the Function address space by default.872 /// is then loaded using OpLoad. Such values are loaded into the UniformConstant storage class by default.
816 /// This function should only be called during function code generation.873 /// This function should only be called during function code generation.
817 fn constant(self: *DeclGen, ty: Type, val: Value) !IdRef {874 fn constant(self: *DeclGen, ty: Type, val: Value) !IdRef {
818 const target = self.getTarget();875 const target = self.getTarget();
...@@ -846,53 +903,27 @@ pub const DeclGen = struct {...@@ -846,53 +903,27 @@ pub const DeclGen = struct {
846 }903 }
847 },904 },
848 else => {905 else => {
849 // The value cannot be generated directly, so generate it as an indirect function-local906 // The value cannot be generated directly, so generate it as an indirect constant,
850 // constant, and then perform an OpLoad.907 // and then perform an OpLoad.
851 const ptr_id = self.spv.allocId();
852 const alignment = ty.abiAlignment(target);908 const alignment = ty.abiAlignment(target);
853 try self.lowerIndirectConstant(ptr_id, ty, val, .Function, alignment);909 const global_index = try self.spv.allocGlobal();
910 log.debug("constant {}", .{global_index});
911 const ptr_id = self.spv.beginGlobal(global_index);
912 defer self.spv.endGlobal();
913 try self.lowerIndirectConstant(ptr_id, ty, val, .UniformConstant, alignment);
854 try self.func.body.emit(self.spv.gpa, .OpLoad, .{914 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
855 .id_result_type = result_ty_id,915 .id_result_type = result_ty_id,
856 .id_result = result_id,916 .id_result = result_id,
857 .pointer = ptr_id,917 .pointer = ptr_id,
858 });918 });
859 // TODO: Convert bools? This logic should hook into `load`.919 // TODO: Convert bools? This logic should hook into `load`. It should be a dead
920 // path though considering .Bool is handled above.
860 },921 },
861 }922 }
862923
863 return result_id;924 return result_id;
864 }925 }
865926
866 fn genDeclRef(self: *DeclGen, result_ty_ref: SpvType.Ref, result_id: IdRef, decl_index: Decl.Index) Error!void {
867 // TODO: Clean up
868 const decl = self.module.declPtr(decl_index);
869 self.module.markDeclAlive(decl);
870 // _ = result_ty_ref;
871 // const decl_id = try self.constant(decl.ty, decl.val, .indirect);
872 // try self.variable(.global, result_id, result_ty_ref, decl_id);
873 const result_storage_class = self.spv.typeRefType(result_ty_ref).payload(.pointer).storage_class;
874 const indirect_result_id = if (result_storage_class != .CrossWorkgroup)
875 self.spv.allocId()
876 else
877 result_id;
878
879 try self.lowerIndirectConstant(
880 indirect_result_id,
881 decl.ty,
882 decl.val,
883 .CrossWorkgroup, // TODO: Make this .Function if required
884 decl.@"align",
885 );
886 const section = &self.spv.sections.types_globals_constants;
887 if (result_storage_class != .CrossWorkgroup) {
888 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
889 .id_result_type = self.typeId(result_ty_ref),
890 .id_result = result_id,
891 .pointer = indirect_result_id,
892 });
893 }
894 }
895
896 /// Turn a Zig type into a SPIR-V Type, and return its type result-id.927 /// Turn a Zig type into a SPIR-V Type, and return its type result-id.
897 fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType {928 fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType {
898 const type_ref = try self.resolveType(ty, .direct);929 const type_ref = try self.resolveType(ty, .direct);
...@@ -996,7 +1027,7 @@ pub const DeclGen = struct {...@@ -996,7 +1027,7 @@ pub const DeclGen = struct {
9961027
997 /// Turn a Zig type into a SPIR-V Type, and return a reference to it.1028 /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
998 fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!SpvType.Ref {1029 fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!SpvType.Ref {
999 log.debug("resolveType: ty = {}", .{ty.fmtDebug()});1030 log.debug("resolveType: ty = {}", .{ty.fmt(self.module)});
1000 const target = self.getTarget();1031 const target = self.getTarget();
1001 switch (ty.zigTypeTag()) {1032 switch (ty.zigTypeTag()) {
1002 .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)),1033 .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)),
...@@ -1042,23 +1073,30 @@ pub const DeclGen = struct {...@@ -1042,23 +1073,30 @@ pub const DeclGen = struct {
1042 };1073 };
1043 return try self.spv.arrayType(total_len, elem_ty_ref);1074 return try self.spv.arrayType(total_len, elem_ty_ref);
1044 },1075 },
1045 .Fn => {1076 .Fn => switch (repr) {
1046 // TODO: Put this somewhere in Sema.zig1077 .direct => {
1047 if (ty.fnIsVarArgs())1078 // TODO: Put this somewhere in Sema.zig
1048 return self.fail("VarArgs functions are unsupported for SPIR-V", .{});1079 if (ty.fnIsVarArgs())
1080 return self.fail("VarArgs functions are unsupported for SPIR-V", .{});
10491081
1050 // TODO: Parameter passing convention etc.1082 // TODO: Parameter passing convention etc.
10511083
1052 const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen());1084 const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen());
1053 for (param_types, 0..) |*param, i| {1085 for (param_types, 0..) |*param, i| {
1054 param.* = try self.resolveType(ty.fnParamType(i), .direct);1086 param.* = try self.resolveType(ty.fnParamType(i), .direct);
1055 }1087 }
10561088
1057 const return_type = try self.resolveType(ty.fnReturnType(), .direct);1089 const return_type = try self.resolveType(ty.fnReturnType(), .direct);
10581090
1059 const payload = try self.spv.arena.create(SpvType.Payload.Function);1091 const payload = try self.spv.arena.create(SpvType.Payload.Function);
1060 payload.* = .{ .return_type = return_type, .parameters = param_types };1092 payload.* = .{ .return_type = return_type, .parameters = param_types };
1061 return try self.spv.resolveType(SpvType.initPayload(&payload.base));1093 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
1094 },
1095 .indirect => {
1096 // TODO: Represent function pointers properly.
1097 // For now, just use an usize type.
1098 return try self.sizeType();
1099 },
1062 },1100 },
1063 .Pointer => {1101 .Pointer => {
1064 const ptr_info = ty.ptrInfo().data;1102 const ptr_info = ty.ptrInfo().data;
...@@ -1196,14 +1234,16 @@ pub const DeclGen = struct {...@@ -1196,14 +1234,16 @@ pub const DeclGen = struct {
11961234
1197 fn genDecl(self: *DeclGen) !void {1235 fn genDecl(self: *DeclGen) !void {
1198 const decl = self.module.declPtr(self.decl_index);1236 const decl = self.module.declPtr(self.decl_index);
1199 const result_id = try self.resolveDecl(self.decl_index);1237 const link = try self.resolveDecl(self.decl_index);
12001238
1201 if (decl.val.castTag(.function)) |_| {1239 if (decl.val.castTag(.function)) |_| {
1240 log.debug("genDecl function {s} = {}", .{decl.name, link.func.result_id.id});
1241
1202 assert(decl.ty.zigTypeTag() == .Fn);1242 assert(decl.ty.zigTypeTag() == .Fn);
1203 const prototype_id = try self.resolveTypeId(decl.ty);1243 const prototype_id = try self.resolveTypeId(decl.ty);
1204 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{1244 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{
1205 .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()),1245 .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()),
1206 .id_result = result_id,1246 .id_result = link.func.result_id,
1207 .function_control = .{}, // TODO: We can set inline here if the type requires it.1247 .function_control = .{}, // TODO: We can set inline here if the type requires it.
1208 .function_type = prototype_id,1248 .function_type = prototype_id,
1209 });1249 });
...@@ -1243,7 +1283,7 @@ pub const DeclGen = struct {...@@ -1243,7 +1283,7 @@ pub const DeclGen = struct {
1243 defer self.module.gpa.free(fqn);1283 defer self.module.gpa.free(fqn);
12441284
1245 try self.spv.sections.debug_names.emit(self.gpa, .OpName, .{1285 try self.spv.sections.debug_names.emit(self.gpa, .OpName, .{
1246 .target = result_id,1286 .target = link.func.result_id,
1247 .name = fqn,1287 .name = fqn,
1248 });1288 });
1249 } else {1289 } else {
...@@ -1264,9 +1304,13 @@ pub const DeclGen = struct {...@@ -1264,9 +1304,13 @@ pub const DeclGen = struct {
1264 else => storage_class,1304 else => storage_class,
1265 };1305 };
12661306
1307 const global_result_id = self.spv.beginGlobal(link.global);
1308 defer self.spv.endGlobal();
1309 log.debug("genDecl {}", .{link.global});
1310
1267 const var_result_id = switch (storage_class) {1311 const var_result_id = switch (storage_class) {
1268 .Generic => self.spv.allocId(),1312 .Generic => self.spv.allocId(),
1269 else => result_id,1313 else => global_result_id,
1270 };1314 };
12711315
1272 try self.lowerIndirectConstant(1316 try self.lowerIndirectConstant(
...@@ -1278,12 +1322,13 @@ pub const DeclGen = struct {...@@ -1278,12 +1322,13 @@ pub const DeclGen = struct {
1278 );1322 );
12791323
1280 if (storage_class == .Generic) {1324 if (storage_class == .Generic) {
1281 const section = &self.spv.sections.types_globals_constants;1325 const section = &self.spv.globals.section;
1282 const ty_ref = try self.resolveType(decl.ty, .indirect);1326 const ty_ref = try self.resolveType(decl.ty, .indirect);
1283 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align");1327 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align");
1328 // TODO: Can we eliminate this cast?
1284 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{1329 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
1285 .id_result_type = self.typeId(ptr_ty_ref),1330 .id_result_type = self.typeId(ptr_ty_ref),
1286 .id_result = result_id,1331 .id_result = global_result_id,
1287 .pointer = var_result_id,1332 .pointer = var_result_id,
1288 });1333 });
1289 }1334 }
...@@ -1972,6 +2017,7 @@ pub const DeclGen = struct {...@@ -1972,6 +2017,7 @@ pub const DeclGen = struct {
1972 .id_result = result_id,2017 .id_result = result_id,
1973 .pointer = alloc_result_id,2018 .pointer = alloc_result_id,
1974 }),2019 }),
2020 // TODO: Can we do without this cast or move it to runtime?
1975 else => try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{2021 else => try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
1976 .id_result_type = self.typeId(ptr_ty_ref),2022 .id_result_type = self.typeId(ptr_ty_ref),
1977 .id_result = result_id,2023 .id_result = result_id,
src/codegen/spirv/Module.zig+126-1
...@@ -55,6 +55,27 @@ pub const Fn = struct {...@@ -55,6 +55,27 @@ pub const Fn = struct {
55 }55 }
56};56};
5757
58/// Globals must be kept in order: operations involving globals must be ordered
59/// so that the global declaration precedes any usage.
60pub const Global = struct {
61 /// Index type to refer to a global by.
62 pub const Index = enum(u32) { _ };
63
64 /// The result-id to be used for this global declaration. Note that this does not
65 /// necessarily refer to an OpVariable instruction - it may also be the final result
66 /// id of a number of OpSpecConstantOp instructions.
67 result_id: IdRef,
68 /// The offset into `self.globals.section` of the first instruction of this global
69 /// declaration.
70 begin_inst: u32,
71 /// The past-end offset into `self.flobals.section`.
72 end_inst: u32,
73 /// The first dependency in the `self.globals.dependencies` array list.
74 begin_dep: u32,
75 /// The past-end dependency in `self.globals.dependencies`.
76 end_dep: u32,
77};
78
58/// A general-purpose allocator which may be used to allocate resources for this module79/// A general-purpose allocator which may be used to allocate resources for this module
59gpa: Allocator,80gpa: Allocator,
6081
...@@ -102,6 +123,20 @@ source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},...@@ -102,6 +123,20 @@ source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
102/// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref).123/// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref).
103type_cache: TypeCache = .{},124type_cache: TypeCache = .{},
104125
126/// The fields in this structure help to maintain the required order for global variables.
127globals: struct {
128 /// The graph nodes of global variables present in the module.
129 nodes: std.ArrayListUnmanaged(Global) = .{},
130 /// This pseudo-section contains the initialization code for all the globals. Instructions from
131 /// here are reordered when flushing the module. Its contents should be part of the
132 /// `types_globals_constants` SPIR-V section.
133 section: Section = .{},
134 /// Holds a list of dependent global variables for each global variable.
135 dependencies: std.ArrayListUnmanaged(Global.Index) = .{},
136 /// The global that initialization code/dependencies are currently being generated for, if any.
137 current_global: ?Global.Index = null,
138} = .{},
139
105pub fn init(gpa: Allocator, arena: Allocator) Module {140pub fn init(gpa: Allocator, arena: Allocator) Module {
106 return .{141 return .{
107 .gpa = gpa,142 .gpa = gpa,
...@@ -124,6 +159,10 @@ pub fn deinit(self: *Module) void {...@@ -124,6 +159,10 @@ pub fn deinit(self: *Module) void {
124 self.source_file_names.deinit(self.gpa);159 self.source_file_names.deinit(self.gpa);
125 self.type_cache.deinit(self.gpa);160 self.type_cache.deinit(self.gpa);
126161
162 self.globals.nodes.deinit(self.gpa);
163 self.globals.section.deinit(self.gpa);
164 self.globals.dependencies.deinit(self.gpa);
165
127 self.* = undefined;166 self.* = undefined;
128}167}
129168
...@@ -141,18 +180,60 @@ pub fn idBound(self: Module) Word {...@@ -141,18 +180,60 @@ pub fn idBound(self: Module) Word {
141 return self.next_result_id;180 return self.next_result_id;
142}181}
143182
183fn orderGlobalsInto(
184 self: Module,
185 global_index: Global.Index,
186 section: *Section,
187 seen: *std.DynamicBitSetUnmanaged,
188) !void {
189 const node = self.globals.nodes.items[@enumToInt(global_index)];
190 const deps = self.globals.dependencies.items[node.begin_dep .. node.end_dep];
191 const insts = self.globals.section.instructions.items[node.begin_inst .. node.end_inst];
192
193 seen.set(@enumToInt(global_index));
194
195 for (deps) |dep| {
196 if (!seen.isSet(@enumToInt(dep))) {
197 try self.orderGlobalsInto(dep, section, seen);
198 }
199 }
200
201 try section.instructions.appendSlice(self.gpa, insts);
202}
203
204fn orderGlobals(self: Module) !Section {
205 const nodes = self.globals.nodes.items;
206
207 var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, nodes.len);
208 defer seen.deinit(self.gpa);
209
210 var ordered_globals = Section{};
211
212 for (0..nodes.len) |global_index| {
213 if (!seen.isSet(global_index)) {
214 try self.orderGlobalsInto(@intToEnum(Global.Index, @intCast(u32, global_index)), &ordered_globals, &seen);
215 }
216 }
217
218 return ordered_globals;
219}
220
144/// Emit this module as a spir-v binary.221/// Emit this module as a spir-v binary.
145pub fn flush(self: Module, file: std.fs.File) !void {222pub fn flush(self: Module, file: std.fs.File) !void {
146 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"223 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
147224
148 const header = [_]Word{225 const header = [_]Word{
149 spec.magic_number,226 spec.magic_number,
150 (1 << 16) | (5 << 8),227 (1 << 16) | (4 << 8), // TODO: From cpu features
151 0, // TODO: Register Zig compiler magic number.228 0, // TODO: Register Zig compiler magic number.
152 self.idBound(),229 self.idBound(),
153 0, // Schema (currently reserved for future use)230 0, // Schema (currently reserved for future use)
154 };231 };
155232
233 // TODO: Perform topological sort on the globals.
234 var globals = try self.orderGlobals();
235 defer globals.deinit(self.gpa);
236
156 // Note: needs to be kept in order according to section 2.3!237 // Note: needs to be kept in order according to section 2.3!
157 const buffers = &[_][]const Word{238 const buffers = &[_][]const Word{
158 &header,239 &header,
...@@ -164,6 +245,7 @@ pub fn flush(self: Module, file: std.fs.File) !void {...@@ -164,6 +245,7 @@ pub fn flush(self: Module, file: std.fs.File) !void {
164 self.sections.debug_names.toWords(),245 self.sections.debug_names.toWords(),
165 self.sections.annotations.toWords(),246 self.sections.annotations.toWords(),
166 self.sections.types_globals_constants.toWords(),247 self.sections.types_globals_constants.toWords(),
248 globals.toWords(),
167 self.sections.functions.toWords(),249 self.sections.functions.toWords(),
168 };250 };
169251
...@@ -279,6 +361,8 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType {...@@ -279,6 +361,8 @@ pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType {
279 .i64,361 .i64,
280 .int,362 .int,
281 => {363 => {
364 // TODO: Kernels do not support OpTypeInt that is signed. We can probably
365 // can get rid of the signedness all together, in Shaders also.
282 const bits = ty.intFloatBits();366 const bits = ty.intFloatBits();
283 const signedness: spec.LiteralInteger = switch (ty.intSignedness()) {367 const signedness: spec.LiteralInteger = switch (ty.intSignedness()) {
284 .unsigned => 0,368 .unsigned => 0,
...@@ -634,3 +718,44 @@ pub fn decorateMember(...@@ -634,3 +718,44 @@ pub fn decorateMember(
634 .decoration = decoration,718 .decoration = decoration,
635 });719 });
636}720}
721
722pub fn allocGlobal(self: *Module) !Global.Index {
723 try self.globals.nodes.append(self.gpa, .{
724 .result_id = self.allocId(),
725 .begin_inst = undefined,
726 .end_inst = undefined,
727 .begin_dep = undefined,
728 .end_dep = undefined,
729 });
730 return @intToEnum(Global.Index, @intCast(u32, self.globals.nodes.items.len - 1));
731}
732
733pub fn globalPtr(self: *Module, index: Global.Index) *Global {
734 return &self.globals.nodes.items[@enumToInt(index)];
735}
736
737/// Begin generating the global for `index`. The previous global is finalized
738/// at this point, and the global for `index` is made active. Any new calls to
739/// `addGlobalDependency` will affect this global. After a new call to this function,
740/// the prior active global cannot be modified again.
741pub fn beginGlobal(self: *Module, index: Global.Index) IdRef {
742 const global = self.globalPtr(index);
743 global.begin_inst = @intCast(u32, self.globals.section.instructions.items.len);
744 global.begin_dep = @intCast(u32, self.globals.dependencies.items.len);
745 self.globals.current_global = index;
746 return global.result_id;
747}
748
749/// Finalize the global. After this point, the current global cannot be modified anymore.
750pub fn endGlobal(self: *Module) void {
751 const global = self.globalPtr(self.globals.current_global.?);
752 global.end_inst = @intCast(u32, self.globals.section.instructions.items.len);
753 global.end_dep = @intCast(u32, self.globals.dependencies.items.len);
754 self.globals.current_global = null;
755}
756
757pub fn addGlobalDependency(self: *Module, dependency: Global.Index) !void {
758 assert(self.globals.current_global != null);
759 assert(self.globals.current_global.? != dependency);
760 try self.globals.dependencies.append(self.gpa, dependency);
761}
src/link/SpirV.zig+5-5
...@@ -46,7 +46,7 @@ base: link.File,...@@ -46,7 +46,7 @@ base: link.File,
4646
47spv: SpvModule,47spv: SpvModule,
48spv_arena: ArenaAllocator,48spv_arena: ArenaAllocator,
49decl_ids: codegen.DeclMap,49decl_link: codegen.DeclLinkMap,
5050
51pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {51pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {
52 const self = try gpa.create(SpirV);52 const self = try gpa.create(SpirV);
...@@ -59,7 +59,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {...@@ -59,7 +59,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {
59 },59 },
60 .spv = undefined,60 .spv = undefined,
61 .spv_arena = ArenaAllocator.init(gpa),61 .spv_arena = ArenaAllocator.init(gpa),
62 .decl_ids = codegen.DeclMap.init(self.base.allocator),62 .decl_link = codegen.DeclLinkMap.init(self.base.allocator),
63 };63 };
64 self.spv = SpvModule.init(gpa, self.spv_arena.allocator());64 self.spv = SpvModule.init(gpa, self.spv_arena.allocator());
65 errdefer self.deinit();65 errdefer self.deinit();
...@@ -100,7 +100,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option...@@ -100,7 +100,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
100pub fn deinit(self: *SpirV) void {100pub fn deinit(self: *SpirV) void {
101 self.spv.deinit();101 self.spv.deinit();
102 self.spv_arena.deinit();102 self.spv_arena.deinit();
103 self.decl_ids.deinit();103 self.decl_link.deinit();
104}104}
105105
106pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {106pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
...@@ -108,7 +108,7 @@ pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liv...@@ -108,7 +108,7 @@ pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liv
108 @panic("Attempted to compile for architecture that was disabled by build configuration");108 @panic("Attempted to compile for architecture that was disabled by build configuration");
109 }109 }
110110
111 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_ids);111 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link);
112 defer decl_gen.deinit();112 defer decl_gen.deinit();
113113
114 if (try decl_gen.gen(func.owner_decl, air, liveness)) |msg| {114 if (try decl_gen.gen(func.owner_decl, air, liveness)) |msg| {
...@@ -121,7 +121,7 @@ pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index)...@@ -121,7 +121,7 @@ pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index)
121 @panic("Attempted to compile for architecture that was disabled by build configuration");121 @panic("Attempted to compile for architecture that was disabled by build configuration");
122 }122 }
123123
124 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_ids);124 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link);
125 defer decl_gen.deinit();125 defer decl_gen.deinit();
126126
127 if (try decl_gen.gen(decl_index, undefined, undefined)) |msg| {127 if (try decl_gen.gen(decl_index, undefined, undefined)) |msg| {