| ... | @@ -40,34 +40,92 @@ pub fn writeInstruction(code: *std.ArrayList(Word), opcode: Opcode, args: []cons | ... | @@ -40,34 +40,92 @@ pub fn writeInstruction(code: *std.ArrayList(Word), opcode: Opcode, args: []cons |
| 40 | try code.appendSlice(args); | 40 | try code.appendSlice(args); |
| 41 | } | 41 | } |
| 42 | | 42 | |
| | 43 | pub fn writeInstructionWithString(code: *std.ArrayList(Word), opcode: Opcode, args: []const Word, str: []const u8) !void { |
| | 44 | // Str needs to be written zero-terminated, so we need to add one to the length. |
| | 45 | const zero_terminated_len = str.len + 1; |
| | 46 | const str_words = (zero_terminated_len + @sizeOf(Word) - 1) / @sizeOf(Word); |
| | 47 | |
| | 48 | try writeOpcode(code, opcode, @intCast(u16, args.len + str_words)); |
| | 49 | try code.ensureUnusedCapacity(args.len + str_words); |
| | 50 | code.appendSliceAssumeCapacity(args); |
| | 51 | |
| | 52 | // TODO: Not actually sure whether this is correct for big-endian. |
| | 53 | // See https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#Literal |
| | 54 | var i: usize = 0; |
| | 55 | while (i < zero_terminated_len) : (i += @sizeOf(Word)) { |
| | 56 | var word: Word = 0; |
| | 57 | |
| | 58 | var j: usize = 0; |
| | 59 | while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) { |
| | 60 | word |= @as(Word, str[i + j]) << @intCast(std.math.Log2Int(Word), j * std.meta.bitCount(u8)); |
| | 61 | } |
| | 62 | |
| | 63 | code.appendAssumeCapacity(word); |
| | 64 | } |
| | 65 | } |
| | 66 | |
| 43 | /// This structure represents a SPIR-V (binary) module being compiled, and keeps track of all relevant information. | 67 | /// This structure represents a SPIR-V (binary) module being compiled, and keeps track of all relevant information. |
| 44 | /// That includes the actual instructions, the current result-id bound, and data structures for querying result-id's | 68 | /// That includes the actual instructions, the current result-id bound, and data structures for querying result-id's |
| 45 | /// of data which needs to be persistent over different calls to Decl code generation. | 69 | /// of data which needs to be persistent over different calls to Decl code generation. |
| 46 | pub const SPIRVModule = struct { | 70 | pub const SPIRVModule = struct { |
| | 71 | /// A general-purpose allocator which may be used to allocate temporary resources required for compilation. |
| | 72 | gpa: *Allocator, |
| | 73 | |
| | 74 | /// The parent module. |
| | 75 | module: *Module, |
| | 76 | |
| | 77 | /// SPIR-V instructions return result-ids. This variable holds the module-wide counter for these. |
| 47 | next_result_id: ResultId, | 78 | next_result_id: ResultId, |
| 48 | | 79 | |
| | 80 | /// Code of the actual SPIR-V binary, divided into the relevant logical sections. |
| | 81 | /// Note: To save some bytes, these could also be unmanaged, but since there is only one instance of SPIRVModule |
| | 82 | /// and this removes some clutter in the rest of the backend, it's fine like this. |
| 49 | binary: struct { | 83 | binary: struct { |
| | 84 | /// OpCapability and OpExtension instructions (in that order). |
| | 85 | capabilities_and_extensions: std.ArrayList(Word), |
| | 86 | |
| | 87 | /// OpString, OpSourceExtension, OpSource, OpSourceContinued. |
| | 88 | debug_strings: std.ArrayList(Word), |
| | 89 | |
| | 90 | /// Type declaration instructions, constant instructions, global variable declarations, OpUndef instructions. |
| 50 | types_globals_constants: std.ArrayList(Word), | 91 | types_globals_constants: std.ArrayList(Word), |
| | 92 | |
| | 93 | /// Regular functions. |
| 51 | fn_decls: std.ArrayList(Word), | 94 | fn_decls: std.ArrayList(Word), |
| 52 | }, | 95 | }, |
| 53 | | 96 | |
| | 97 | /// Global type cache to reduce the amount of generated types. |
| 54 | types: TypeMap, | 98 | types: TypeMap, |
| 55 | | 99 | |
| 56 | pub fn init(gpa: *Allocator) SPIRVModule { | 100 | /// Cache for results of OpString instructions for module file names fed to OpSource. |
| | 101 | /// Since OpString is pretty much only used for those, we don't need to keep track of all strings, |
| | 102 | /// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource. |
| | 103 | file_names: std.StringHashMap(ResultId), |
| | 104 | |
| | 105 | pub fn init(gpa: *Allocator, module: *Module) SPIRVModule { |
| 57 | return .{ | 106 | return .{ |
| | 107 | .gpa = gpa, |
| | 108 | .module = module, |
| 58 | .next_result_id = 1, // 0 is an invalid SPIR-V result ID. | 109 | .next_result_id = 1, // 0 is an invalid SPIR-V result ID. |
| 59 | .binary = .{ | 110 | .binary = .{ |
| | 111 | .capabilities_and_extensions = std.ArrayList(Word).init(gpa), |
| | 112 | .debug_strings = std.ArrayList(Word).init(gpa), |
| 60 | .types_globals_constants = std.ArrayList(Word).init(gpa), | 113 | .types_globals_constants = std.ArrayList(Word).init(gpa), |
| 61 | .fn_decls = std.ArrayList(Word).init(gpa), | 114 | .fn_decls = std.ArrayList(Word).init(gpa), |
| 62 | }, | 115 | }, |
| 63 | .types = TypeMap.init(gpa), | 116 | .types = TypeMap.init(gpa), |
| | 117 | .file_names = std.StringHashMap(ResultId).init(gpa), |
| 64 | }; | 118 | }; |
| 65 | } | 119 | } |
| 66 | | 120 | |
| 67 | pub fn deinit(self: *SPIRVModule) void { | 121 | pub fn deinit(self: *SPIRVModule) void { |
| 68 | self.binary.types_globals_constants.deinit(); | 122 | self.file_names.deinit(); |
| 69 | self.binary.fn_decls.deinit(); | | |
| 70 | self.types.deinit(); | 123 | self.types.deinit(); |
| | 124 | |
| | 125 | self.binary.fn_decls.deinit(); |
| | 126 | self.binary.types_globals_constants.deinit(); |
| | 127 | self.binary.debug_strings.deinit(); |
| | 128 | self.binary.capabilities_and_extensions.deinit(); |
| 71 | } | 129 | } |
| 72 | | 130 | |
| 73 | pub fn allocResultId(self: *SPIRVModule) Word { | 131 | pub fn allocResultId(self: *SPIRVModule) Word { |
| ... | @@ -78,13 +136,26 @@ pub const SPIRVModule = struct { | ... | @@ -78,13 +136,26 @@ pub const SPIRVModule = struct { |
| 78 | pub fn resultIdBound(self: *SPIRVModule) Word { | 136 | pub fn resultIdBound(self: *SPIRVModule) Word { |
| 79 | return self.next_result_id; | 137 | return self.next_result_id; |
| 80 | } | 138 | } |
| | 139 | |
| | 140 | fn resolveSourceFileName(self: *SPIRVModule, decl: *Decl) !ResultId { |
| | 141 | const path = decl.namespace.file_scope.sub_file_path; |
| | 142 | const result = try self.file_names.getOrPut(path); |
| | 143 | if (!result.found_existing) { |
| | 144 | result.entry.value = self.allocResultId(); |
| | 145 | try writeInstructionWithString(&self.binary.debug_strings, .OpString, &[_]Word{result.entry.value}, path); |
| | 146 | try writeInstruction(&self.binary.debug_strings, .OpSource, &[_]Word{ |
| | 147 | @enumToInt(spec.SourceLanguage.Unknown), // TODO: Register Zig source language. |
| | 148 | 0, // TODO: Zig version as u32? |
| | 149 | result.entry.value, |
| | 150 | }); |
| | 151 | } |
| | 152 | |
| | 153 | return result.entry.value; |
| | 154 | } |
| 81 | }; | 155 | }; |
| 82 | | 156 | |
| 83 | /// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that. | 157 | /// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that. |
| 84 | pub const DeclGen = struct { | 158 | pub const DeclGen = struct { |
| 85 | /// The parent module. | | |
| 86 | module: *Module, | | |
| 87 | | | |
| 88 | /// The SPIR-V module code should be put in. | 159 | /// The SPIR-V module code should be put in. |
| 89 | spv: *SPIRVModule, | 160 | spv: *SPIRVModule, |
| 90 | | 161 | |
| ... | @@ -158,9 +229,8 @@ pub const DeclGen = struct { | ... | @@ -158,9 +229,8 @@ pub const DeclGen = struct { |
| 158 | }; | 229 | }; |
| 159 | | 230 | |
| 160 | /// Initialize the common resources of a DeclGen. Some fields are left uninitialized, only set when `gen` is called. | 231 | /// Initialize the common resources of a DeclGen. Some fields are left uninitialized, only set when `gen` is called. |
| 161 | pub fn init(gpa: *Allocator, module: *Module, spv: *SPIRVModule) DeclGen { | 232 | pub fn init(gpa: *Allocator, spv: *SPIRVModule) DeclGen { |
| 162 | return .{ | 233 | return .{ |
| 163 | .module = module, | | |
| 164 | .spv = spv, | 234 | .spv = spv, |
| 165 | .args = std.ArrayList(ResultId).init(gpa), | 235 | .args = std.ArrayList(ResultId).init(gpa), |
| 166 | .next_arg_index = undefined, | 236 | .next_arg_index = undefined, |
| ... | @@ -196,10 +266,14 @@ pub const DeclGen = struct { | ... | @@ -196,10 +266,14 @@ pub const DeclGen = struct { |
| 196 | self.blocks.deinit(); | 266 | self.blocks.deinit(); |
| 197 | } | 267 | } |
| 198 | | 268 | |
| | 269 | fn getTarget(self: *DeclGen) std.Target { |
| | 270 | return self.spv.module.getTarget(); |
| | 271 | } |
| | 272 | |
| 199 | fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) Error { | 273 | fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) Error { |
| 200 | @setCold(true); | 274 | @setCold(true); |
| 201 | const src_loc = src.toSrcLocWithDecl(self.decl); | 275 | const src_loc = src.toSrcLocWithDecl(self.decl); |
| 202 | self.error_msg = try Module.ErrorMsg.create(self.module.gpa, src_loc, format, args); | 276 | self.error_msg = try Module.ErrorMsg.create(self.spv.module.gpa, src_loc, format, args); |
| 203 | return error.AnalysisFail; | 277 | return error.AnalysisFail; |
| 204 | } | 278 | } |
| 205 | | 279 | |
| ... | @@ -227,7 +301,7 @@ pub const DeclGen = struct { | ... | @@ -227,7 +301,7 @@ pub const DeclGen = struct { |
| 227 | /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers). | 301 | /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers). |
| 228 | /// TODO: Should the result of this function be cached? | 302 | /// TODO: Should the result of this function be cached? |
| 229 | fn backingIntBits(self: *DeclGen, bits: u16) ?u16 { | 303 | fn backingIntBits(self: *DeclGen, bits: u16) ?u16 { |
| 230 | const target = self.module.getTarget(); | 304 | const target = self.getTarget(); |
| 231 | | 305 | |
| 232 | // The backend will never be asked to compiler a 0-bit integer, so we won't have to handle those in this function. | 306 | // The backend will never be asked to compiler a 0-bit integer, so we won't have to handle those in this function. |
| 233 | std.debug.assert(bits != 0); | 307 | std.debug.assert(bits != 0); |
| ... | @@ -262,7 +336,7 @@ pub const DeclGen = struct { | ... | @@ -262,7 +336,7 @@ pub const DeclGen = struct { |
| 262 | /// is no way of knowing whether those are actually supported. | 336 | /// is no way of knowing whether those are actually supported. |
| 263 | /// TODO: Maybe this should be cached? | 337 | /// TODO: Maybe this should be cached? |
| 264 | fn largestSupportedIntBits(self: *DeclGen) u16 { | 338 | fn largestSupportedIntBits(self: *DeclGen) u16 { |
| 265 | const target = self.module.getTarget(); | 339 | const target = self.getTarget(); |
| 266 | return if (Target.spirv.featureSetHas(target.cpu.features, .Int64)) | 340 | return if (Target.spirv.featureSetHas(target.cpu.features, .Int64)) |
| 267 | 64 | 341 | 64 |
| 268 | else | 342 | else |
| ... | @@ -277,7 +351,7 @@ pub const DeclGen = struct { | ... | @@ -277,7 +351,7 @@ pub const DeclGen = struct { |
| 277 | } | 351 | } |
| 278 | | 352 | |
| 279 | fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo { | 353 | fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo { |
| 280 | const target = self.module.getTarget(); | 354 | const target = self.getTarget(); |
| 281 | return switch (ty.zigTypeTag()) { | 355 | return switch (ty.zigTypeTag()) { |
| 282 | .Bool => ArithmeticTypeInfo{ | 356 | .Bool => ArithmeticTypeInfo{ |
| 283 | .bits = 1, // Doesn't matter for this class. | 357 | .bits = 1, // Doesn't matter for this class. |
| ... | @@ -313,7 +387,7 @@ pub const DeclGen = struct { | ... | @@ -313,7 +387,7 @@ pub const DeclGen = struct { |
| 313 | /// Generate a constant representing `val`. | 387 | /// Generate a constant representing `val`. |
| 314 | /// TODO: Deduplication? | 388 | /// TODO: Deduplication? |
| 315 | fn genConstant(self: *DeclGen, src: LazySrcLoc, ty: Type, val: Value) Error!ResultId { | 389 | fn genConstant(self: *DeclGen, src: LazySrcLoc, ty: Type, val: Value) Error!ResultId { |
| 316 | const target = self.module.getTarget(); | 390 | const target = self.getTarget(); |
| 317 | const code = &self.spv.binary.types_globals_constants; | 391 | const code = &self.spv.binary.types_globals_constants; |
| 318 | const result_id = self.spv.allocResultId(); | 392 | const result_id = self.spv.allocResultId(); |
| 319 | const result_type_id = try self.genType(src, ty); | 393 | const result_type_id = try self.genType(src, ty); |
| ... | @@ -398,7 +472,7 @@ pub const DeclGen = struct { | ... | @@ -398,7 +472,7 @@ pub const DeclGen = struct { |
| 398 | return already_generated; | 472 | return already_generated; |
| 399 | } | 473 | } |
| 400 | | 474 | |
| 401 | const target = self.module.getTarget(); | 475 | const target = self.getTarget(); |
| 402 | const code = &self.spv.binary.types_globals_constants; | 476 | const code = &self.spv.binary.types_globals_constants; |
| 403 | const result_id = self.spv.allocResultId(); | 477 | const result_id = self.spv.allocResultId(); |
| 404 | | 478 | |
| ... | @@ -587,7 +661,7 @@ pub const DeclGen = struct { | ... | @@ -587,7 +661,7 @@ pub const DeclGen = struct { |
| 587 | .breakpoint => null, | 661 | .breakpoint => null, |
| 588 | .condbr => try self.genCondBr(inst.castTag(.condbr).?), | 662 | .condbr => try self.genCondBr(inst.castTag(.condbr).?), |
| 589 | .constant => unreachable, | 663 | .constant => unreachable, |
| 590 | .dbg_stmt => null, | 664 | .dbg_stmt => try self.genDbgStmt(inst.castTag(.dbg_stmt).?), |
| 591 | .load => try self.genLoad(inst.castTag(.load).?), | 665 | .load => try self.genLoad(inst.castTag(.load).?), |
| 592 | .loop => try self.genLoop(inst.castTag(.loop).?), | 666 | .loop => try self.genLoop(inst.castTag(.loop).?), |
| 593 | .ret => try self.genRet(inst.castTag(.ret).?), | 667 | .ret => try self.genRet(inst.castTag(.ret).?), |
| ... | @@ -748,7 +822,7 @@ pub const DeclGen = struct { | ... | @@ -748,7 +822,7 @@ pub const DeclGen = struct { |
| 748 | const label_id = self.spv.allocResultId(); | 822 | const label_id = self.spv.allocResultId(); |
| 749 | | 823 | |
| 750 | // 4 chosen as arbitrary initial capacity. | 824 | // 4 chosen as arbitrary initial capacity. |
| 751 | var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.module.gpa, 4); | 825 | var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.spv.gpa, 4); |
| 752 | | 826 | |
| 753 | try self.blocks.putNoClobber(inst, .{ | 827 | try self.blocks.putNoClobber(inst, .{ |
| 754 | .label_id = label_id, | 828 | .label_id = label_id, |
| ... | @@ -756,7 +830,7 @@ pub const DeclGen = struct { | ... | @@ -756,7 +830,7 @@ pub const DeclGen = struct { |
| 756 | }); | 830 | }); |
| 757 | defer { | 831 | defer { |
| 758 | self.blocks.removeAssertDiscard(inst); | 832 | self.blocks.removeAssertDiscard(inst); |
| 759 | incoming_blocks.deinit(self.module.gpa); | 833 | incoming_blocks.deinit(self.spv.gpa); |
| 760 | } | 834 | } |
| 761 | | 835 | |
| 762 | try self.genBody(inst.body); | 836 | try self.genBody(inst.body); |
| ... | @@ -792,7 +866,7 @@ pub const DeclGen = struct { | ... | @@ -792,7 +866,7 @@ pub const DeclGen = struct { |
| 792 | if (inst.operand.ty.hasCodeGenBits()) { | 866 | if (inst.operand.ty.hasCodeGenBits()) { |
| 793 | const operand_id = try self.resolve(inst.operand); | 867 | const operand_id = try self.resolve(inst.operand); |
| 794 | // current_block_label_id should not be undefined here, lest there is a br or br_void in the function's body. | 868 | // current_block_label_id should not be undefined here, lest there is a br or br_void in the function's body. |
| 795 | try target.incoming_blocks.append(self.module.gpa, .{ | 869 | try target.incoming_blocks.append(self.spv.gpa, .{ |
| 796 | .src_label_id = self.current_block_label_id, | 870 | .src_label_id = self.current_block_label_id, |
| 797 | .break_value_id = operand_id | 871 | .break_value_id = operand_id |
| 798 | }); | 872 | }); |
| ... | @@ -836,6 +910,12 @@ pub const DeclGen = struct { | ... | @@ -836,6 +910,12 @@ pub const DeclGen = struct { |
| 836 | return null; | 910 | return null; |
| 837 | } | 911 | } |
| 838 | | 912 | |
| | 913 | fn genDbgStmt(self: *DeclGen, inst: *Inst.DbgStmt) !?ResultId { |
| | 914 | const src_fname_id = try self.spv.resolveSourceFileName(self.decl); |
| | 915 | try writeInstruction(&self.spv.binary.fn_decls, .OpLine, &[_]Word{ src_fname_id, inst.line, inst.column }); |
| | 916 | return null; |
| | 917 | } |
| | 918 | |
| 839 | fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId { | 919 | fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId { |
| 840 | const operand_id = try self.resolve(inst.operand); | 920 | const operand_id = try self.resolve(inst.operand); |
| 841 | | 921 | |