authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-01-21 20:14:31+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-01-28 14:38:58+01:00
log1b6ebce0da45169979b0f51a07274ff6fb5590bc
tree5e5e4c3c2eb9d87fec61e39b7a5f1d081a75ec88
parent72e67aaf05de06e5a7b74596e70ce5afd17b2919

spirv: new module

This introduces a dedicated struct that handles module-wide information.

4 files changed, 522 insertions(+), 405 deletions(-)

src/codegen/spirv.zig+291-332
...@@ -4,9 +4,6 @@ const Target = std.Target;...@@ -4,9 +4,6 @@ const Target = std.Target;
4const log = std.log.scoped(.codegen);4const log = std.log.scoped(.codegen);
5const assert = std.debug.assert;5const assert = std.debug.assert;
66
7const spec = @import("spirv/spec.zig");
8const Opcode = spec.Opcode;
9
10const Module = @import("../Module.zig");7const Module = @import("../Module.zig");
11const Decl = Module.Decl;8const Decl = Module.Decl;
12const Type = @import("../type.zig").Type;9const Type = @import("../type.zig").Type;
...@@ -15,180 +12,75 @@ const LazySrcLoc = Module.LazySrcLoc;...@@ -15,180 +12,75 @@ const LazySrcLoc = Module.LazySrcLoc;
15const Air = @import("../Air.zig");12const Air = @import("../Air.zig");
16const Liveness = @import("../Liveness.zig");13const Liveness = @import("../Liveness.zig");
1714
18pub const Word = u32;15const spec = @import("spirv/spec.zig");
19pub const ResultId = u32;16const Opcode = spec.Opcode;
17const Word = spec.Word;
18const IdRef = spec.IdRef;
19const IdResult = spec.IdResult;
20const IdResultType = spec.IdResultType;
21
22const SpvModule = @import("spirv/Module.zig");
23const SpvSection = @import("spirv/Section.zig");
2024
21pub const TypeMap = std.HashMap(Type, u32, Type.HashContext64, std.hash_map.default_max_load_percentage);25const TypeCache = std.HashMapUnmanaged(Type, IdResultType, Type.HashContext64, std.hash_map.default_max_load_percentage);
22pub const InstMap = std.AutoHashMap(Air.Inst.Index, ResultId);26const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
2327
24const IncomingBlock = struct {28const IncomingBlock = struct {
25 src_label_id: ResultId,29 src_label_id: IdRef,
26 break_value_id: ResultId,30 break_value_id: IdRef,
27};31};
2832
29pub const BlockMap = std.AutoHashMap(Air.Inst.Index, struct {33pub const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
30 label_id: ResultId,34 label_id: IdRef,
31 incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),35 incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),
32});36});
3337
34pub fn writeOpcode(code: *std.ArrayList(Word), opcode: Opcode, arg_count: u16) !void {38/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
35 const word_count: Word = arg_count + 1;39pub const DeclGen = struct {
36 try code.append((word_count << 16) | @enumToInt(opcode));40 /// The Zig module that we are generating decls for.
37}
38
39pub fn writeInstruction(code: *std.ArrayList(Word), opcode: Opcode, args: []const Word) !void {
40 try writeOpcode(code, opcode, @intCast(u16, args.len));
41 try code.appendSlice(args);
42}
43
44pub fn writeInstructionWithString(code: *std.ArrayList(Word), opcode: Opcode, args: []const Word, str: []const u8) !void {
45 // Str needs to be written zero-terminated, so we need to add one to the length.
46 const zero_terminated_len = str.len + 1;
47 const str_words = (zero_terminated_len + @sizeOf(Word) - 1) / @sizeOf(Word);
48
49 try writeOpcode(code, opcode, @intCast(u16, args.len + str_words));
50 try code.ensureUnusedCapacity(args.len + str_words);
51 code.appendSliceAssumeCapacity(args);
52
53 // TODO: Not actually sure whether this is correct for big-endian.
54 // See https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#Literal
55 var i: usize = 0;
56 while (i < zero_terminated_len) : (i += @sizeOf(Word)) {
57 var word: Word = 0;
58
59 var j: usize = 0;
60 while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) {
61 word |= @as(Word, str[i + j]) << @intCast(std.math.Log2Int(Word), j * std.meta.bitCount(u8));
62 }
63
64 code.appendAssumeCapacity(word);
65 }
66}
67
68/// This structure represents a SPIR-V (binary) module being compiled, and keeps track of all relevant information.
69/// That includes the actual instructions, the current result-id bound, and data structures for querying result-id's
70/// of data which needs to be persistent over different calls to Decl code generation.
71pub const SPIRVModule = struct {
72 /// A general-purpose allocator which may be used to allocate temporary resources required for compilation.
73 gpa: Allocator,
74
75 /// The parent module.
76 module: *Module,41 module: *Module,
7742
78 /// SPIR-V instructions return result-ids. This variable holds the module-wide counter for these.43 /// The SPIR-V module code should be put in.
79 next_result_id: ResultId,44 spv: *SpvModule,
80
81 /// Code of the actual SPIR-V binary, divided into the relevant logical sections.
82 /// Note: To save some bytes, these could also be unmanaged, but since there is only one instance of SPIRVModule
83 /// and this removes some clutter in the rest of the backend, it's fine like this.
84 binary: struct {
85 /// OpCapability and OpExtension instructions (in that order).
86 capabilities_and_extensions: std.ArrayList(Word),
87
88 /// OpString, OpSourceExtension, OpSource, OpSourceContinued.
89 debug_strings: std.ArrayList(Word),
90
91 /// Type declaration instructions, constant instructions, global variable declarations, OpUndef instructions.
92 types_globals_constants: std.ArrayList(Word),
93
94 /// Regular functions.
95 fn_decls: std.ArrayList(Word),
96 },
97
98 /// Global type cache to reduce the amount of generated types.
99 types: TypeMap,
100
101 /// Cache for results of OpString instructions for module file names fed to OpSource.
102 /// Since OpString is pretty much only used for those, we don't need to keep track of all strings,
103 /// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.
104 file_names: std.StringHashMap(ResultId),
105
106 pub fn init(gpa: Allocator, module: *Module) SPIRVModule {
107 return .{
108 .gpa = gpa,
109 .module = module,
110 .next_result_id = 1, // 0 is an invalid SPIR-V result ID.
111 .binary = .{
112 .capabilities_and_extensions = std.ArrayList(Word).init(gpa),
113 .debug_strings = std.ArrayList(Word).init(gpa),
114 .types_globals_constants = std.ArrayList(Word).init(gpa),
115 .fn_decls = std.ArrayList(Word).init(gpa),
116 },
117 .types = TypeMap.init(gpa),
118 .file_names = std.StringHashMap(ResultId).init(gpa),
119 };
120 }
121
122 pub fn deinit(self: *SPIRVModule) void {
123 self.file_names.deinit();
124 self.types.deinit();
125
126 self.binary.fn_decls.deinit();
127 self.binary.types_globals_constants.deinit();
128 self.binary.debug_strings.deinit();
129 self.binary.capabilities_and_extensions.deinit();
130 }
131
132 pub fn allocResultId(self: *SPIRVModule) Word {
133 defer self.next_result_id += 1;
134 return self.next_result_id;
135 }
136
137 pub fn resultIdBound(self: *SPIRVModule) Word {
138 return self.next_result_id;
139 }
140
141 fn resolveSourceFileName(self: *SPIRVModule, decl: *Decl) !ResultId {
142 const path = decl.getFileScope().sub_file_path;
143 const result = try self.file_names.getOrPut(path);
144 if (!result.found_existing) {
145 result.value_ptr.* = self.allocResultId();
146 try writeInstructionWithString(&self.binary.debug_strings, .OpString, &[_]Word{result.value_ptr.*}, path);
147 try writeInstruction(&self.binary.debug_strings, .OpSource, &[_]Word{
148 @enumToInt(spec.SourceLanguage.Unknown), // TODO: Register Zig source language.
149 0, // TODO: Zig version as u32?
150 result.value_ptr.*,
151 });
152 }
153
154 return result.value_ptr.*;
155 }
156};
15745
158/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.46 /// The decl we are currently generating code for.
159pub const DeclGen = struct {47 decl: *Decl,
160 /// The SPIR-V module code should be put in.
161 spv: *SPIRVModule,
16248
49 /// The intermediate code of the declaration we are currently generating. Note: If
50 /// the declaration is not a function, this value will be undefined!
163 air: Air,51 air: Air,
52
53 /// The liveness analysis of the intermediate code for the declaration we are currently generating.
54 /// Note: If the declaration is not a function, this value will be undefined!
164 liveness: Liveness,55 liveness: Liveness,
16556
166 /// An array of function argument result-ids. Each index corresponds with the57 /// An array of function argument result-ids. Each index corresponds with the
167 /// function argument of the same index.58 /// function argument of the same index.
168 args: std.ArrayList(ResultId),59 args: std.ArrayListUnmanaged(IdRef) = .{},
16960
170 /// A counter to keep track of how many `arg` instructions we've seen yet.61 /// A counter to keep track of how many `arg` instructions we've seen yet.
171 next_arg_index: u32,62 next_arg_index: u32,
17263
64 /// A cache for zig types to prevent having to re-process a particular type. This structure is kept around
65 /// after a call to `gen` so that they don't have to be re-resolved for different decls.
66 type_cache: TypeCache = .{},
67
173 /// A map keeping track of which instruction generated which result-id.68 /// A map keeping track of which instruction generated which result-id.
174 inst_results: InstMap,69 inst_results: InstMap = .{},
17570
176 /// We need to keep track of result ids for block labels, as well as the 'incoming'71 /// We need to keep track of result ids for block labels, as well as the 'incoming'
177 /// blocks for a block.72 /// blocks for a block.
178 blocks: BlockMap,73 blocks: BlockMap = .{},
17974
180 /// The label of the SPIR-V block we are currently generating.75 /// The label of the SPIR-V block we are currently generating.
181 current_block_label_id: ResultId,76 current_block_label_id: IdRef,
18277
183 /// The actual instructions for this function. We need to declare all locals in78 /// The actual instructions for this function. We need to declare all locals in
184 /// the first block, and because we don't know which locals there are going to be,79 /// the first block, and because we don't know which locals there are going to be,
185 /// we're just going to generate everything after the locals-section in this array.80 /// we're just going to generate everything after the locals-section in this array.
186 /// Note: It will not contain OpFunction, OpFunctionParameter, OpVariable and the81 /// Note: It will not contain OpFunction, OpFunctionParameter, OpVariable and the
187 /// initial OpLabel. These will be generated into spv.binary.fn_decls directly.82 /// initial OpLabel. These will be generated into spv.sections.functions directly.
188 code: std.ArrayList(Word),83 code: SpvSection = .{},
189
190 /// The decl we are currently generating code for.
191 decl: *Decl,
19284
193 /// If `gen` returned `Error.CodegenFail`, this contains an explanatory message.85 /// If `gen` returned `Error.CodegenFail`, this contains an explanatory message.
194 /// Memory is owned by `module.gpa`.86 /// Memory is owned by `module.gpa`.
...@@ -244,18 +136,15 @@ pub const DeclGen = struct {...@@ -244,18 +136,15 @@ pub const DeclGen = struct {
244136
245 /// Initialize the common resources of a DeclGen. Some fields are left uninitialized,137 /// Initialize the common resources of a DeclGen. Some fields are left uninitialized,
246 /// only set when `gen` is called.138 /// only set when `gen` is called.
247 pub fn init(spv: *SPIRVModule) DeclGen {139 pub fn init(module: *Module, spv: *SpvModule) DeclGen {
248 return .{140 return .{
141 .module = module,
249 .spv = spv,142 .spv = spv,
143 .decl = undefined,
250 .air = undefined,144 .air = undefined,
251 .liveness = undefined,145 .liveness = undefined,
252 .args = std.ArrayList(ResultId).init(spv.gpa),
253 .next_arg_index = undefined,146 .next_arg_index = undefined,
254 .inst_results = InstMap.init(spv.gpa),
255 .blocks = BlockMap.init(spv.gpa),
256 .current_block_label_id = undefined,147 .current_block_label_id = undefined,
257 .code = std.ArrayList(Word).init(spv.gpa),
258 .decl = undefined,
259 .error_msg = undefined,148 .error_msg = undefined,
260 };149 };
261 }150 }
...@@ -265,15 +154,16 @@ pub const DeclGen = struct {...@@ -265,15 +154,16 @@ pub const DeclGen = struct {
265 /// returns such a reportable error, it is valid to be called again for a different decl.154 /// returns such a reportable error, it is valid to be called again for a different decl.
266 pub fn gen(self: *DeclGen, decl: *Decl, air: Air, liveness: Liveness) !?*Module.ErrorMsg {155 pub fn gen(self: *DeclGen, decl: *Decl, air: Air, liveness: Liveness) !?*Module.ErrorMsg {
267 // Reset internal resources, we don't want to re-allocate these.156 // Reset internal resources, we don't want to re-allocate these.
157 self.decl = decl;
268 self.air = air;158 self.air = air;
269 self.liveness = liveness;159 self.liveness = liveness;
270 self.args.items.len = 0;160 self.args.items.len = 0;
271 self.next_arg_index = 0;161 self.next_arg_index = 0;
162 // Note: don't clear type_cache.
272 self.inst_results.clearRetainingCapacity();163 self.inst_results.clearRetainingCapacity();
273 self.blocks.clearRetainingCapacity();164 self.blocks.clearRetainingCapacity();
274 self.current_block_label_id = undefined;165 self.current_block_label_id = undefined;
275 self.code.items.len = 0;166 self.code.reset();
276 self.decl = decl;
277 self.error_msg = null;167 self.error_msg = null;
278168
279 self.genDecl() catch |err| switch (err) {169 self.genDecl() catch |err| switch (err) {
...@@ -286,25 +176,38 @@ pub const DeclGen = struct {...@@ -286,25 +176,38 @@ pub const DeclGen = struct {
286176
287 /// Free resources owned by the DeclGen.177 /// Free resources owned by the DeclGen.
288 pub fn deinit(self: *DeclGen) void {178 pub fn deinit(self: *DeclGen) void {
289 self.args.deinit();179 self.args.deinit(self.spv.gpa);
290 self.inst_results.deinit();180 self.type_cache.deinit(self.spv.gpa);
291 self.blocks.deinit();181 self.inst_results.deinit(self.spv.gpa);
292 self.code.deinit();182 self.blocks.deinit(self.spv.gpa);
183 self.code.deinit(self.spv.gpa);
293 }184 }
294185
186 /// Return the target which we are currently compiling for.
295 fn getTarget(self: *DeclGen) std.Target {187 fn getTarget(self: *DeclGen) std.Target {
296 return self.spv.module.getTarget();188 return self.module.getTarget();
297 }189 }
298190
299 fn fail(self: *DeclGen, comptime format: []const u8, args: anytype) Error {191 fn fail(self: *DeclGen, comptime format: []const u8, args: anytype) Error {
300 @setCold(true);192 @setCold(true);
301 const src: LazySrcLoc = .{ .node_offset = 0 };193 const src: LazySrcLoc = .{ .node_offset = 0 };
302 const src_loc = src.toSrcLoc(self.decl);194 const src_loc = src.toSrcLoc(self.decl);
303 self.error_msg = try Module.ErrorMsg.create(self.spv.module.gpa, src_loc, format, args);195 assert(self.error_msg == null);
196 self.error_msg = try Module.ErrorMsg.create(self.module.gpa, src_loc, format, args);
197 return error.CodegenFail;
198 }
199
200 fn todo(self: *DeclGen, comptime format: []const u8, args: anytype) Error {
201 @setCold(true);
202 const src: LazySrcLoc = .{ .node_offset = 0 };
203 const src_loc = src.toSrcLoc(self.decl);
204 assert(self.error_msg == null);
205 self.error_msg = try Module.ErrorMsg.create(self.module.gpa, src_loc, "TODO (SPIR-V): " ++ format, args);
304 return error.CodegenFail;206 return error.CodegenFail;
305 }207 }
306208
307 fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !ResultId {209 /// Fetch the result-id for a previously generated instruction or constant.
210 fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !IdRef {
308 if (self.air.value(inst)) |val| {211 if (self.air.value(inst)) |val| {
309 return self.genConstant(self.air.typeOf(inst), val);212 return self.genConstant(self.air.typeOf(inst), val);
310 }213 }
...@@ -312,9 +215,13 @@ pub const DeclGen = struct {...@@ -312,9 +215,13 @@ pub const DeclGen = struct {
312 return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.215 return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
313 }216 }
314217
315 fn beginSPIRVBlock(self: *DeclGen, label_id: ResultId) !void {218 /// Start a new SPIR-V block, Emits the label of the new block, and stores which
316 try writeInstruction(&self.code, .OpLabel, &[_]Word{label_id});219 /// block we are currently generating.
317 self.current_block_label_id = label_id;220 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to
221 /// keep track of the previous block.
222 fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void {
223 try self.code.emit(self.spv.gpa, .OpLabel, .{.id_result = label_id});
224 self.current_block_label_id = label_id.toRef();
318 }225 }
319226
320 /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need227 /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need
...@@ -396,13 +303,18 @@ pub const DeclGen = struct {...@@ -396,13 +303,18 @@ pub const DeclGen = struct {
396 const int_info = ty.intInfo(target);303 const int_info = ty.intInfo(target);
397 // TODO: Maybe it's useful to also return this value.304 // TODO: Maybe it's useful to also return this value.
398 const maybe_backing_bits = self.backingIntBits(int_info.bits);305 const maybe_backing_bits = self.backingIntBits(int_info.bits);
399 break :blk ArithmeticTypeInfo{ .bits = int_info.bits, .is_vector = false, .signedness = int_info.signedness, .class = if (maybe_backing_bits) |backing_bits|306 break :blk ArithmeticTypeInfo{
400 if (backing_bits == int_info.bits)307 .bits = int_info.bits,
401 ArithmeticTypeInfo.Class.integer308 .is_vector = false,
309 .signedness = int_info.signedness,
310 .class = if (maybe_backing_bits) |backing_bits|
311 if (backing_bits == int_info.bits)
312 ArithmeticTypeInfo.Class.integer
313 else
314 ArithmeticTypeInfo.Class.strange_integer
402 else315 else
403 ArithmeticTypeInfo.Class.strange_integer316 .composite_integer,
404 else317 };
405 .composite_integer };
406 },318 },
407 // As of yet, there is no vector support in the self-hosted compiler.319 // As of yet, there is no vector support in the self-hosted compiler.
408 .Vector => self.fail("TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}),320 .Vector => self.fail("TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}),
...@@ -413,15 +325,15 @@ pub const DeclGen = struct {...@@ -413,15 +325,15 @@ pub const DeclGen = struct {
413325
414 /// Generate a constant representing `val`.326 /// Generate a constant representing `val`.
415 /// TODO: Deduplication?327 /// TODO: Deduplication?
416 fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!ResultId {328 fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!IdRef {
417 const target = self.getTarget();329 const target = self.getTarget();
418 const code = &self.spv.binary.types_globals_constants;330 const section = &self.spv.sections.types_globals_constants;
419 const result_id = self.spv.allocResultId();331 const result_id = self.spv.allocId();
420 const result_type_id = try self.genType(ty);332 const result_type_id = try self.genType(ty);
421333
422 if (val.isUndef()) {334 if (val.isUndef()) {
423 try writeInstruction(code, .OpUndef, &[_]Word{ result_type_id, result_id });335 try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_type_id, .id_result = result_id });
424 return result_id;336 return result_id.toRef();
425 }337 }
426338
427 switch (ty.zigTypeTag()) {339 switch (ty.zigTypeTag()) {
...@@ -436,76 +348,71 @@ pub const DeclGen = struct {...@@ -436,76 +348,71 @@ pub const DeclGen = struct {
436 // SPIR-V native type (up to i/u64 with Int64). If SPIR-V ever supports native ints of a larger size, this348 // SPIR-V native type (up to i/u64 with Int64). If SPIR-V ever supports native ints of a larger size, this
437 // might need to be updated.349 // might need to be updated.
438 assert(self.largestSupportedIntBits() <= std.meta.bitCount(u64));350 assert(self.largestSupportedIntBits() <= std.meta.bitCount(u64));
351
352 // Note, value is required to be sign-extended, so we don't need to mask off the upper bits.
353 // See https://www.khronos.org/registry/SPIR-V/specs/unified1/SPIRV.html#Literal
439 var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt()) else val.toUnsignedInt();354 var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt()) else val.toUnsignedInt();
440355
441 // Mask the low bits which make up the actual integer. This is to make sure that negative values356 const value: spec.LiteralContextDependentNumber = switch (backing_bits) {
442 // only use the actual bits of the type.357 1...32 => .{.uint32 = @truncate(u32, int_bits)},
443 // TODO: Should this be the backing type bits or the actual type bits?358 33...64 => .{.uint64 = int_bits},
444 int_bits &= (@as(u64, 1) << @intCast(u6, backing_bits)) - 1;359 else => unreachable,
445360 };
446 switch (backing_bits) {361
447 0 => unreachable,362 try section.emit(self.spv.gpa, .OpConstant, .{
448 1...32 => try writeInstruction(code, .OpConstant, &[_]Word{363 .id_result_type = result_type_id,
449 result_type_id,364 .id_result = result_id,
450 result_id,365 .value = value,
451 @truncate(u32, int_bits),366 });
452 }),
453 33...64 => try writeInstruction(code, .OpConstant, &[_]Word{
454 result_type_id,
455 result_id,
456 @truncate(u32, int_bits),
457 @truncate(u32, int_bits >> @bitSizeOf(u32)),
458 }),
459 else => unreachable, // backing_bits is bounded by largestSupportedIntBits.
460 }
461 },367 },
462 .Bool => {368 .Bool => {
463 const opcode: Opcode = if (val.toBool()) .OpConstantTrue else .OpConstantFalse;369 const operands = .{ .id_result_type = result_type_id, .id_result = result_id };
464 try writeInstruction(code, opcode, &[_]Word{ result_type_id, result_id });370 if (val.toBool()) {
371 try section.emit(self.spv.gpa, .OpConstantTrue, operands);
372 } else {
373 try section.emit(self.spv.gpa, .OpConstantFalse, operands);
374 }
465 },375 },
466 .Float => {376 .Float => {
467 // At this point we are guaranteed that the target floating point type is supported, otherwise the function377 // At this point we are guaranteed that the target floating point type is supported, otherwise the function
468 // would have exited at genType(ty).378 // would have exited at genType(ty).
469379
470 // f16 and f32 require one word of storage. f64 requires 2, low-order first.380 const value: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) {
471381 // Prevent upcasting to f32 by bitcasting and writing as a uint32.
472 switch (ty.floatBits(target)) {382 16 => .{.uint32 = @bitCast(u16, val.toFloat(f16))},
473 16 => try writeInstruction(code, .OpConstant, &[_]Word{ result_type_id, result_id, @bitCast(u16, val.toFloat(f16)) }),383 32 => .{.float32 = val.toFloat(f32)},
474 32 => try writeInstruction(code, .OpConstant, &[_]Word{ result_type_id, result_id, @bitCast(u32, val.toFloat(f32)) }),384 64 => .{.float64 = val.toFloat(f64)},
475 64 => {
476 const float_bits = @bitCast(u64, val.toFloat(f64));
477 try writeInstruction(code, .OpConstant, &[_]Word{
478 result_type_id,
479 result_id,
480 @truncate(u32, float_bits),
481 @truncate(u32, float_bits >> @bitSizeOf(u32)),
482 });
483 },
484 128 => unreachable, // Filtered out in the call to genType.385 128 => unreachable, // Filtered out in the call to genType.
485 // TODO: Insert case for long double when the layout for that is determined.386 // TODO: Insert case for long double when the layout for that is determined?
486 else => unreachable,387 else => unreachable,
487 }388 };
389
390 try section.emit(self.spv.gpa, .OpConstant, .{
391 .id_result_type = result_type_id,
392 .id_result = result_id,
393 .value = value,
394 });
488 },395 },
489 .Void => unreachable,396 .Void => unreachable,
490 else => return self.fail("TODO: SPIR-V backend: constant generation of type {}", .{ty}),397 else => return self.fail("TODO: SPIR-V backend: constant generation of type {}", .{ty}),
491 }398 }
492399
493 return result_id;400 return result_id.toRef();
494 }401 }
495402
496 fn genType(self: *DeclGen, ty: Type) Error!ResultId {403 fn genType(self: *DeclGen, ty: Type) Error!IdResultType {
497 // We can't use getOrPut here so we can recursively generate types.404 // We can't use getOrPut here so we can recursively generate types.
498 if (self.spv.types.get(ty)) |already_generated| {405 if (self.type_cache.get(ty)) |already_generated| {
499 return already_generated;406 return already_generated;
500 }407 }
501408
502 const target = self.getTarget();409 const target = self.getTarget();
503 const code = &self.spv.binary.types_globals_constants;410 const section = &self.spv.sections.types_globals_constants;
504 const result_id = self.spv.allocResultId();411 const result_id = self.spv.allocId();
505412
506 switch (ty.zigTypeTag()) {413 switch (ty.zigTypeTag()) {
507 .Void => try writeInstruction(code, .OpTypeVoid, &[_]Word{result_id}),414 .Void => try section.emit(self.spv.gpa, .OpTypeVoid, .{.id_result = result_id}),
508 .Bool => try writeInstruction(code, .OpTypeBool, &[_]Word{result_id}),415 .Bool => try section.emit(self.spv.gpa, .OpTypeBool, .{.id_result = result_id}),
509 .Int => {416 .Int => {
510 const int_info = ty.intInfo(target);417 const int_info = ty.intInfo(target);
511 const backing_bits = self.backingIntBits(int_info.bits) orelse {418 const backing_bits = self.backingIntBits(int_info.bits) orelse {
...@@ -514,11 +421,11 @@ pub const DeclGen = struct {...@@ -514,11 +421,11 @@ pub const DeclGen = struct {
514 };421 };
515422
516 // TODO: If backing_bits != int_info.bits, a duplicate type might be generated here.423 // TODO: If backing_bits != int_info.bits, a duplicate type might be generated here.
517 try writeInstruction(code, .OpTypeInt, &[_]Word{424 try section.emit(self.spv.gpa, .OpTypeInt, .{
518 result_id,425 .id_result = result_id,
519 backing_bits,426 .width = backing_bits,
520 switch (int_info.signedness) {427 .signedness = switch (int_info.signedness) {
521 .unsigned => 0,428 .unsigned => @as(spec.LiteralInteger, 0),
522 .signed => 1,429 .signed => 1,
523 },430 },
524 });431 });
...@@ -539,7 +446,7 @@ pub const DeclGen = struct {...@@ -539,7 +446,7 @@ pub const DeclGen = struct {
539 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});446 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
540 }447 }
541448
542 try writeInstruction(code, .OpTypeFloat, &[_]Word{ result_id, bits });449 try section.emit(self.spv.gpa, .OpTypeFloat, .{.id_result = result_id, .width = bits});
543 },450 },
544 .Fn => {451 .Fn => {
545 // We only support zig-calling-convention functions, no varargs.452 // We only support zig-calling-convention functions, no varargs.
...@@ -558,14 +465,16 @@ pub const DeclGen = struct {...@@ -558,14 +465,16 @@ pub const DeclGen = struct {
558465
559 const return_type_id = try self.genType(ty.fnReturnType());466 const return_type_id = try self.genType(ty.fnReturnType());
560467
468 try section.emitRaw(self.spv.gpa, .OpTypeFunction, 2 + @intCast(u16, ty.fnParamLen()));
469
561 // result id + result type id + parameter type ids.470 // result id + result type id + parameter type ids.
562 try writeOpcode(code, .OpTypeFunction, 2 + @intCast(u16, ty.fnParamLen()));471 section.writeOperand(IdResult, result_id);
563 try code.appendSlice(&.{ result_id, return_type_id });472 section.writeOperand(IdResultType, return_type_id);
564473
565 i = 0;474 i = 0;
566 while (i < params) : (i += 1) {475 while (i < params) : (i += 1) {
567 const param_type_id = self.spv.types.get(ty.fnParamType(i)).?;476 const param_type_id = self.type_cache.get(ty.fnParamType(i)).?;
568 try code.append(param_type_id);477 section.writeOperand(IdRef, param_type_id.toRef());
569 }478 }
570 },479 },
571 // When recursively generating a type, we cannot infer the pointer's storage class. See genPointerType.480 // When recursively generating a type, we cannot infer the pointer's storage class. See genPointerType.
...@@ -594,26 +503,29 @@ pub const DeclGen = struct {...@@ -594,26 +503,29 @@ pub const DeclGen = struct {
594 else => |tag| return self.fail("TODO: SPIR-V backend: implement type {}s", .{tag}),503 else => |tag| return self.fail("TODO: SPIR-V backend: implement type {}s", .{tag}),
595 }504 }
596505
597 try self.spv.types.putNoClobber(ty, result_id);506 try self.type_cache.putNoClobber(self.spv.gpa, ty, result_id.toResultType());
598 return result_id;507 return result_id.toResultType();
599 }508 }
600509
601 /// SPIR-V requires pointers to have a storage class (address space), and so we have a special function for that.510 /// SPIR-V requires pointers to have a storage class (address space), and so we have a special function for that.
602 /// TODO: The result of this needs to be cached.511 /// TODO: The result of this needs to be cached.
603 fn genPointerType(self: *DeclGen, ty: Type, storage_class: spec.StorageClass) !ResultId {512 fn genPointerType(self: *DeclGen, ty: Type, storage_class: spec.StorageClass) !IdResultType {
604 assert(ty.zigTypeTag() == .Pointer);513 assert(ty.zigTypeTag() == .Pointer);
605514
606 const code = &self.spv.binary.types_globals_constants;515 const result_id = self.spv.allocId();
607 const result_id = self.spv.allocResultId();
608516
609 // TODO: There are many constraints which are ignored for now: We may only create pointers to certain types, and to other types517 // TODO: There are many constraints which are ignored for now: We may only create pointers to certain types, and to other types
610 // if more capabilities are enabled. For example, we may only create pointers to f16 if Float16Buffer is enabled.518 // if more capabilities are enabled. For example, we may only create pointers to f16 if Float16Buffer is enabled.
611 // These also relates to the pointer's address space.519 // These also relates to the pointer's address space.
612 const child_id = try self.genType(ty.elemType());520 const child_id = try self.genType(ty.elemType());
613521
614 try writeInstruction(code, .OpTypePointer, &[_]Word{ result_id, @enumToInt(storage_class), child_id });522 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{
523 .id_result = result_id,
524 .storage_class = storage_class,
525 .type = child_id.toRef(),
526 });
615527
616 return result_id;528 return result_id.toResultType();
617 }529 }
618530
619 fn genDecl(self: *DeclGen) !void {531 fn genDecl(self: *DeclGen) !void {
...@@ -623,38 +535,43 @@ pub const DeclGen = struct {...@@ -623,38 +535,43 @@ pub const DeclGen = struct {
623 if (decl.val.castTag(.function)) |_| {535 if (decl.val.castTag(.function)) |_| {
624 assert(decl.ty.zigTypeTag() == .Fn);536 assert(decl.ty.zigTypeTag() == .Fn);
625 const prototype_id = try self.genType(decl.ty);537 const prototype_id = try self.genType(decl.ty);
626 try writeInstruction(&self.spv.binary.fn_decls, .OpFunction, &[_]Word{538 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunction, .{
627 self.spv.types.get(decl.ty.fnReturnType()).?, // This type should be generated along with the prototype.539 .id_result_type = self.type_cache.get(decl.ty.fnReturnType()).?, // This type should be generated along with the prototype.
628 result_id,540 .id_result = result_id,
629 @bitCast(Word, spec.FunctionControl{}), // TODO: We can set inline here if the type requires it.541 .function_control = .{}, // TODO: We can set inline here if the type requires it.
630 prototype_id,542 .function_type = prototype_id.toRef(),
631 });543 });
632544
633 const params = decl.ty.fnParamLen();545 const params = decl.ty.fnParamLen();
634 var i: usize = 0;546 var i: usize = 0;
635547
636 try self.args.ensureUnusedCapacity(params);548 try self.args.ensureUnusedCapacity(self.spv.gpa, params);
637 while (i < params) : (i += 1) {549 while (i < params) : (i += 1) {
638 const param_type_id = self.spv.types.get(decl.ty.fnParamType(i)).?;550 const param_type_id = self.type_cache.get(decl.ty.fnParamType(i)).?;
639 const arg_result_id = self.spv.allocResultId();551 const arg_result_id = self.spv.allocId();
640 try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionParameter, &[_]Word{ param_type_id, arg_result_id });552 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionParameter, .{
641 self.args.appendAssumeCapacity(arg_result_id);553 .id_result_type = param_type_id,
554 .id_result = arg_result_id,
555 });
556 self.args.appendAssumeCapacity(arg_result_id.toRef());
642 }557 }
643558
644 // TODO: This could probably be done in a better way...559 // TODO: This could probably be done in a better way...
645 const root_block_id = self.spv.allocResultId();560 const root_block_id = self.spv.allocId();
646561
647 // We need to generate the label directly in the fn_decls here because we're going to write the local variables after562 // We need to generate the label directly in the functions section here because we're going to write the local variables after
648 // here. Since we're not generating in self.code, we're just going to bypass self.beginSPIRVBlock here.563 // here. Since we're not generating in self.code, we're just going to bypass self.beginSpvBlock here.
649 try writeInstruction(&self.spv.binary.fn_decls, .OpLabel, &[_]Word{root_block_id});564 try self.spv.sections.functions.emit(self.spv.gpa, .OpLabel, .{
650 self.current_block_label_id = root_block_id;565 .id_result = root_block_id,
566 });
567 self.current_block_label_id = root_block_id.toRef();
651568
652 const main_body = self.air.getMainBody();569 const main_body = self.air.getMainBody();
653 try self.genBody(main_body);570 try self.genBody(main_body);
654571
655 // Append the actual code into the fn_decls section.572 // Append the actual code into the functions section.
656 try self.spv.binary.fn_decls.appendSlice(self.code.items);573 try self.spv.sections.functions.append(self.spv.gpa, self.code);
657 try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionEnd, &[_]Word{});574 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionEnd, {});
658 } else {575 } else {
659 return self.fail("TODO: SPIR-V backend: generate decl type {}", .{decl.ty.zigTypeTag()});576 return self.fail("TODO: SPIR-V backend: generate decl type {}", .{decl.ty.zigTypeTag()});
660 }577 }
...@@ -670,9 +587,9 @@ pub const DeclGen = struct {...@@ -670,9 +587,9 @@ pub const DeclGen = struct {
670 const air_tags = self.air.instructions.items(.tag);587 const air_tags = self.air.instructions.items(.tag);
671 const result_id = switch (air_tags[inst]) {588 const result_id = switch (air_tags[inst]) {
672 // zig fmt: off589 // zig fmt: off
673 .add, .addwrap => try self.airArithOp(inst, .{.OpFAdd, .OpIAdd, .OpIAdd}),590 .add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd),
674 .sub, .subwrap => try self.airArithOp(inst, .{.OpFSub, .OpISub, .OpISub}),591 .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub),
675 .mul, .mulwrap => try self.airArithOp(inst, .{.OpFMul, .OpIMul, .OpIMul}),592 .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul),
676593
677 .bit_and => try self.airBinOpSimple(inst, .OpBitwiseAnd),594 .bit_and => try self.airBinOpSimple(inst, .OpBitwiseAnd),
678 .bit_or => try self.airBinOpSimple(inst, .OpBitwiseOr),595 .bit_or => try self.airBinOpSimple(inst, .OpBitwiseOr),
...@@ -682,12 +599,12 @@ pub const DeclGen = struct {...@@ -682,12 +599,12 @@ pub const DeclGen = struct {
682599
683 .not => try self.airNot(inst),600 .not => try self.airNot(inst),
684601
685 .cmp_eq => try self.airCmp(inst, .{.OpFOrdEqual, .OpLogicalEqual, .OpIEqual}),602 .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual),
686 .cmp_neq => try self.airCmp(inst, .{.OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual}),603 .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual),
687 .cmp_gt => try self.airCmp(inst, .{.OpFOrdGreaterThan, .OpSGreaterThan, .OpUGreaterThan}),604 .cmp_gt => try self.airCmp(inst, .OpFOrdGreaterThan, .OpSGreaterThan, .OpUGreaterThan),
688 .cmp_gte => try self.airCmp(inst, .{.OpFOrdGreaterThanEqual, .OpSGreaterThanEqual, .OpUGreaterThanEqual}),605 .cmp_gte => try self.airCmp(inst, .OpFOrdGreaterThanEqual, .OpSGreaterThanEqual, .OpUGreaterThanEqual),
689 .cmp_lt => try self.airCmp(inst, .{.OpFOrdLessThan, .OpSLessThan, .OpULessThan}),606 .cmp_lt => try self.airCmp(inst, .OpFOrdLessThan, .OpSLessThan, .OpULessThan),
690 .cmp_lte => try self.airCmp(inst, .{.OpFOrdLessThanEqual, .OpSLessThanEqual, .OpULessThanEqual}),607 .cmp_lte => try self.airCmp(inst, .OpFOrdLessThanEqual, .OpSLessThanEqual, .OpULessThanEqual),
691608
692 .arg => self.airArg(),609 .arg => self.airArg(),
693 .alloc => try self.airAlloc(inst),610 .alloc => try self.airAlloc(inst),
...@@ -710,22 +627,25 @@ pub const DeclGen = struct {...@@ -710,22 +627,25 @@ pub const DeclGen = struct {
710 }),627 }),
711 };628 };
712629
713 try self.inst_results.putNoClobber(inst, result_id);630 try self.inst_results.putNoClobber(self.spv.gpa, inst, result_id);
714 }631 }
715632
716 fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, opcode: Opcode) !ResultId {633 fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !IdRef {
717 const bin_op = self.air.instructions.items(.data)[inst].bin_op;634 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
718 const lhs_id = try self.resolve(bin_op.lhs);635 const lhs_id = try self.resolve(bin_op.lhs);
719 const rhs_id = try self.resolve(bin_op.rhs);636 const rhs_id = try self.resolve(bin_op.rhs);
720 const result_id = self.spv.allocResultId();637 const result_id = self.spv.allocId();
721 const result_type_id = try self.genType(self.air.typeOfIndex(inst));638 const result_type_id = try self.genType(self.air.typeOfIndex(inst));
722 try writeInstruction(&self.code, opcode, &[_]Word{639 try self.code.emit(self.spv.gpa, opcode, .{
723 result_type_id, result_id, lhs_id, rhs_id,640 .id_result_type = result_type_id,
641 .id_result = result_id,
642 .operand_1 = lhs_id,
643 .operand_2 = rhs_id,
724 });644 });
725 return result_id;645 return result_id.toRef();
726 }646 }
727647
728 fn airArithOp(self: *DeclGen, inst: Air.Inst.Index, ops: [3]Opcode) !ResultId {648 fn airArithOp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !IdRef {
729 // LHS and RHS are guaranteed to have the same type, and AIR guarantees649 // LHS and RHS are guaranteed to have the same type, and AIR guarantees
730 // the result to be the same as the LHS and RHS, which matches SPIR-V.650 // the result to be the same as the LHS and RHS, which matches SPIR-V.
731 const ty = self.air.typeOfIndex(inst);651 const ty = self.air.typeOfIndex(inst);
...@@ -733,7 +653,7 @@ pub const DeclGen = struct {...@@ -733,7 +653,7 @@ pub const DeclGen = struct {
733 const lhs_id = try self.resolve(bin_op.lhs);653 const lhs_id = try self.resolve(bin_op.lhs);
734 const rhs_id = try self.resolve(bin_op.rhs);654 const rhs_id = try self.resolve(bin_op.rhs);
735655
736 const result_id = self.spv.allocResultId();656 const result_id = self.spv.allocId();
737 const result_type_id = try self.genType(ty);657 const result_type_id = try self.genType(ty);
738658
739 assert(self.air.typeOf(bin_op.lhs).eql(ty));659 assert(self.air.typeOf(bin_op.lhs).eql(ty));
...@@ -757,20 +677,31 @@ pub const DeclGen = struct {...@@ -757,20 +677,31 @@ pub const DeclGen = struct {
757 .float => 0,677 .float => 0,
758 else => unreachable,678 else => unreachable,
759 };679 };
760 const opcode = ops[opcode_index];
761 try writeInstruction(&self.code, opcode, &[_]Word{ result_type_id, result_id, lhs_id, rhs_id });
762680
681 const operands = .{
682 .id_result_type = result_type_id,
683 .id_result = result_id,
684 .operand_1 = lhs_id,
685 .operand_2 = rhs_id,
686 };
687
688 switch (opcode_index) {
689 0 => try self.code.emit(self.spv.gpa, fop, operands),
690 1 => try self.code.emit(self.spv.gpa, sop, operands),
691 2 => try self.code.emit(self.spv.gpa, uop, operands),
692 else => unreachable,
693 }
763 // TODO: Trap on overflow? Probably going to be annoying.694 // TODO: Trap on overflow? Probably going to be annoying.
764 // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap.695 // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap.
765696
766 return result_id;697 return result_id.toRef();
767 }698 }
768699
769 fn airCmp(self: *DeclGen, inst: Air.Inst.Index, ops: [3]Opcode) !ResultId {700 fn airCmp(self: *DeclGen, inst: Air.Inst.Index, comptime fop: Opcode, comptime sop: Opcode, comptime uop: Opcode) !IdRef {
770 const bin_op = self.air.instructions.items(.data)[inst].bin_op;701 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
771 const lhs_id = try self.resolve(bin_op.lhs);702 const lhs_id = try self.resolve(bin_op.lhs);
772 const rhs_id = try self.resolve(bin_op.rhs);703 const rhs_id = try self.resolve(bin_op.rhs);
773 const result_id = self.spv.allocResultId();704 const result_id = self.spv.allocId();
774 const result_type_id = try self.genType(Type.initTag(.bool));705 const result_type_id = try self.genType(Type.initTag(.bool));
775 const op_ty = self.air.typeOf(bin_op.lhs);706 const op_ty = self.air.typeOf(bin_op.lhs);
776 assert(op_ty.eql(self.air.typeOf(bin_op.rhs)));707 assert(op_ty.eql(self.air.typeOf(bin_op.rhs)));
...@@ -793,53 +724,71 @@ pub const DeclGen = struct {...@@ -793,53 +724,71 @@ pub const DeclGen = struct {
793 .unsigned => @as(usize, 2),724 .unsigned => @as(usize, 2),
794 },725 },
795 };726 };
796 const opcode = ops[opcode_index];
797727
798 try writeInstruction(&self.code, opcode, &[_]Word{ result_type_id, result_id, lhs_id, rhs_id });728 const operands = .{
799 return result_id;729 .id_result_type = result_type_id,
730 .id_result = result_id,
731 .operand_1 = lhs_id,
732 .operand_2 = rhs_id,
733 };
734
735 switch (opcode_index) {
736 0 => try self.code.emit(self.spv.gpa, fop, operands),
737 1 => try self.code.emit(self.spv.gpa, sop, operands),
738 2 => try self.code.emit(self.spv.gpa, uop, operands),
739 else => unreachable,
740 }
741
742 return result_id.toRef();
800 }743 }
801744
802 fn airNot(self: *DeclGen, inst: Air.Inst.Index) !ResultId {745 fn airNot(self: *DeclGen, inst: Air.Inst.Index) !IdRef {
803 const ty_op = self.air.instructions.items(.data)[inst].ty_op;746 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
804 const operand_id = try self.resolve(ty_op.operand);747 const operand_id = try self.resolve(ty_op.operand);
805 const result_id = self.spv.allocResultId();748 const result_id = self.spv.allocId();
806 const result_type_id = try self.genType(Type.initTag(.bool));749 const result_type_id = try self.genType(Type.initTag(.bool));
807 const opcode: Opcode = .OpLogicalNot;750 try self.code.emit(self.spv.gpa, .OpLogicalNot, .{
808 try writeInstruction(&self.code, opcode, &[_]Word{ result_type_id, result_id, operand_id });751 .id_result_type = result_type_id,
809 return result_id;752 .id_result = result_id,
753 .operand = operand_id,
754 });
755 return result_id.toRef();
810 }756 }
811757
812 fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !ResultId {758 fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !IdRef {
813 const ty = self.air.typeOfIndex(inst);759 const ty = self.air.typeOfIndex(inst);
814 const storage_class = spec.StorageClass.Function;760 const storage_class = spec.StorageClass.Function;
815 const result_type_id = try self.genPointerType(ty, storage_class);761 const result_type_id = try self.genPointerType(ty, storage_class);
816 const result_id = self.spv.allocResultId();762 const result_id = self.spv.allocId();
817763
818 // Rather than generating into code here, we're just going to generate directly into the fn_decls section so that764 // Rather than generating into code here, we're just going to generate directly into the functions section so that
819 // variable declarations appear in the first block of the function.765 // variable declarations appear in the first block of the function.
820 try writeInstruction(&self.spv.binary.fn_decls, .OpVariable, &[_]Word{ result_type_id, result_id, @enumToInt(storage_class) });766 try self.spv.sections.functions.emit(self.spv.gpa, .OpVariable, .{
821767 .id_result_type = result_type_id,
822 return result_id;768 .id_result = result_id,
769 .storage_class = storage_class,
770 });
771 return result_id.toRef();
823 }772 }
824773
825 fn airArg(self: *DeclGen) ResultId {774 fn airArg(self: *DeclGen) IdRef {
826 defer self.next_arg_index += 1;775 defer self.next_arg_index += 1;
827 return self.args.items[self.next_arg_index];776 return self.args.items[self.next_arg_index];
828 }777 }
829778
830 fn airBlock(self: *DeclGen, inst: Air.Inst.Index) !?ResultId {779 fn airBlock(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
831 // In IR, a block doesn't really define an entry point like a block, but more like a scope that breaks can jump out of and780 // In AIR, a block doesn't really define an entry point like a block, but more like a scope that breaks can jump out of and
832 // "return" a value from. This cannot be directly modelled in SPIR-V, so in a block instruction, we're going to split up781 // "return" a value from. This cannot be directly modelled in SPIR-V, so in a block instruction, we're going to split up
833 // the current block by first generating the code of the block, then a label, and then generate the rest of the current782 // the current block by first generating the code of the block, then a label, and then generate the rest of the current
834 // ir.Block in a different SPIR-V block.783 // ir.Block in a different SPIR-V block.
835784
836 const label_id = self.spv.allocResultId();785 const label_id = self.spv.allocId();
837786
838 // 4 chosen as arbitrary initial capacity.787 // 4 chosen as arbitrary initial capacity.
839 var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.spv.gpa, 4);788 var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.spv.gpa, 4);
840789
841 try self.blocks.putNoClobber(inst, .{790 try self.blocks.putNoClobber(self.spv.gpa, inst, .{
842 .label_id = label_id,791 .label_id = label_id.toRef(),
843 .incoming_blocks = &incoming_blocks,792 .incoming_blocks = &incoming_blocks,
844 });793 });
845 defer {794 defer {
...@@ -853,7 +802,7 @@ pub const DeclGen = struct {...@@ -853,7 +802,7 @@ pub const DeclGen = struct {
853 const body = self.air.extra[extra.end..][0..extra.data.body_len];802 const body = self.air.extra[extra.end..][0..extra.data.body_len];
854803
855 try self.genBody(body);804 try self.genBody(body);
856 try self.beginSPIRVBlock(label_id);805 try self.beginSpvBlock(label_id);
857806
858 // If this block didn't produce a value, simply return here.807 // If this block didn't produce a value, simply return here.
859 if (!ty.hasRuntimeBits())808 if (!ty.hasRuntimeBits())
...@@ -861,7 +810,7 @@ pub const DeclGen = struct {...@@ -861,7 +810,7 @@ pub const DeclGen = struct {
861810
862 // Combine the result from the blocks using the Phi instruction.811 // Combine the result from the blocks using the Phi instruction.
863812
864 const result_id = self.spv.allocResultId();813 const result_id = self.spv.allocId();
865814
866 // TODO: OpPhi is limited in the types that it may produce, such as pointers. Figure out which other types815 // TODO: OpPhi is limited in the types that it may produce, such as pointers. Figure out which other types
867 // are not allowed to be created from a phi node, and throw an error for those. For now, genType already throws816 // are not allowed to be created from a phi node, and throw an error for those. For now, genType already throws
...@@ -869,13 +818,13 @@ pub const DeclGen = struct {...@@ -869,13 +818,13 @@ pub const DeclGen = struct {
869 const result_type_id = try self.genType(ty);818 const result_type_id = try self.genType(ty);
870 _ = result_type_id;819 _ = result_type_id;
871820
872 try writeOpcode(&self.code, .OpPhi, 2 + @intCast(u16, incoming_blocks.items.len * 2)); // result type + result + variable/parent...821 try self.code.emitRaw(self.spv.gpa, .OpPhi, 2 + @intCast(u16, incoming_blocks.items.len * 2)); // result type + result + variable/parent...
873822
874 for (incoming_blocks.items) |incoming| {823 for (incoming_blocks.items) |incoming| {
875 try self.code.appendSlice(&[_]Word{ incoming.break_value_id, incoming.src_label_id });824 self.code.writeOperand(spec.PairIdRefIdRef, .{ incoming.break_value_id, incoming.src_label_id });
876 }825 }
877826
878 return result_id;827 return result_id.toRef();
879 }828 }
880829
881 fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void {830 fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void {
...@@ -889,7 +838,7 @@ pub const DeclGen = struct {...@@ -889,7 +838,7 @@ pub const DeclGen = struct {
889 try block.incoming_blocks.append(self.spv.gpa, .{ .src_label_id = self.current_block_label_id, .break_value_id = operand_id });838 try block.incoming_blocks.append(self.spv.gpa, .{ .src_label_id = self.current_block_label_id, .break_value_id = operand_id });
890 }839 }
891840
892 try writeInstruction(&self.code, .OpBranch, &[_]Word{block.label_id});841 try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = block.label_id});
893 }842 }
894843
895 fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {844 fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {
...@@ -900,63 +849,70 @@ pub const DeclGen = struct {...@@ -900,63 +849,70 @@ pub const DeclGen = struct {
900 const condition_id = try self.resolve(pl_op.operand);849 const condition_id = try self.resolve(pl_op.operand);
901850
902 // These will always generate a new SPIR-V block, since they are ir.Body and not ir.Block.851 // These will always generate a new SPIR-V block, since they are ir.Body and not ir.Block.
903 const then_label_id = self.spv.allocResultId();852 const then_label_id = self.spv.allocId();
904 const else_label_id = self.spv.allocResultId();853 const else_label_id = self.spv.allocId();
905854
906 // TODO: We can generate OpSelectionMerge here if we know the target block that both of these will resolve to,855 // TODO: We can generate OpSelectionMerge here if we know the target block that both of these will resolve to,
907 // but i don't know if those will always resolve to the same block.856 // but i don't know if those will always resolve to the same block.
908857
909 try writeInstruction(&self.code, .OpBranchConditional, &[_]Word{858 try self.code.emit(self.spv.gpa, .OpBranchConditional, .{
910 condition_id,859 .condition = condition_id,
911 then_label_id,860 .true_label = then_label_id.toRef(),
912 else_label_id,861 .false_label = else_label_id.toRef(),
913 });862 });
914863
915 try self.beginSPIRVBlock(then_label_id);864 try self.beginSpvBlock(then_label_id);
916 try self.genBody(then_body);865 try self.genBody(then_body);
917 try self.beginSPIRVBlock(else_label_id);866 try self.beginSpvBlock(else_label_id);
918 try self.genBody(else_body);867 try self.genBody(else_body);
919 }868 }
920869
921 fn airDbgStmt(self: *DeclGen, inst: Air.Inst.Index) !void {870 fn airDbgStmt(self: *DeclGen, inst: Air.Inst.Index) !void {
922 const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;871 const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
923 const src_fname_id = try self.spv.resolveSourceFileName(self.decl);872 const src_fname_id = try self.spv.resolveSourceFileName(self.decl);
924 try writeInstruction(&self.code, .OpLine, &[_]Word{ src_fname_id, dbg_stmt.line, dbg_stmt.column });873 try self.code.emit(self.spv.gpa, .OpLine, .{
874 .file = src_fname_id,
875 .line = dbg_stmt.line,
876 .column = dbg_stmt.column,
877 });
925 }878 }
926879
927 fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !ResultId {880 fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !IdRef {
928 const ty_op = self.air.instructions.items(.data)[inst].ty_op;881 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
929 const operand_id = try self.resolve(ty_op.operand);882 const operand_id = try self.resolve(ty_op.operand);
930 const ty = self.air.typeOfIndex(inst);883 const ty = self.air.typeOfIndex(inst);
931884
932 const result_type_id = try self.genType(ty);885 const result_type_id = try self.genType(ty);
933 const result_id = self.spv.allocResultId();886 const result_id = self.spv.allocId();
934887
935 const operands = if (ty.isVolatilePtr())888 const access = spec.MemoryAccess.Extended{
936 &[_]Word{ result_type_id, result_id, operand_id, @bitCast(u32, spec.MemoryAccess{ .Volatile = true }) }889 .Volatile = ty.isVolatilePtr(),
937 else890 };
938 &[_]Word{ result_type_id, result_id, operand_id };
939891
940 try writeInstruction(&self.code, .OpLoad, operands);892 try self.code.emit(self.spv.gpa, .OpLoad, .{
893 .id_result_type = result_type_id,
894 .id_result = result_id,
895 .pointer = operand_id,
896 .memory_access = access,
897 });
941898
942 return result_id;899 return result_id.toRef();
943 }900 }
944901
945 fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void {902 fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void {
946 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;903 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
947 const loop = self.air.extraData(Air.Block, ty_pl.payload);904 const loop = self.air.extraData(Air.Block, ty_pl.payload);
948 const body = self.air.extra[loop.end..][0..loop.data.body_len];905 const body = self.air.extra[loop.end..][0..loop.data.body_len];
949 const loop_label_id = self.spv.allocResultId();906 const loop_label_id = self.spv.allocId();
950907
951 // Jump to the loop entry point908 // Jump to the loop entry point
952 try writeInstruction(&self.code, .OpBranch, &[_]Word{loop_label_id});909 try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = loop_label_id.toRef()});
953910
954 // TODO: Look into OpLoopMerge.911 // TODO: Look into OpLoopMerge.
955912 try self.beginSpvBlock(loop_label_id);
956 try self.beginSPIRVBlock(loop_label_id);
957 try self.genBody(body);913 try self.genBody(body);
958914
959 try writeInstruction(&self.code, .OpBranch, &[_]Word{loop_label_id});915 try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = loop_label_id.toRef()});
960 }916 }
961917
962 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {918 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {
...@@ -964,9 +920,9 @@ pub const DeclGen = struct {...@@ -964,9 +920,9 @@ pub const DeclGen = struct {
964 const operand_ty = self.air.typeOf(operand);920 const operand_ty = self.air.typeOf(operand);
965 if (operand_ty.hasRuntimeBits()) {921 if (operand_ty.hasRuntimeBits()) {
966 const operand_id = try self.resolve(operand);922 const operand_id = try self.resolve(operand);
967 try writeInstruction(&self.code, .OpReturnValue, &[_]Word{operand_id});923 try self.code.emit(self.spv.gpa, .OpReturnValue, .{.value = operand_id});
968 } else {924 } else {
969 try writeInstruction(&self.code, .OpReturn, &[_]Word{});925 try self.code.emit(self.spv.gpa, .OpReturn, {});
970 }926 }
971 }927 }
972928
...@@ -976,15 +932,18 @@ pub const DeclGen = struct {...@@ -976,15 +932,18 @@ pub const DeclGen = struct {
976 const src_val_id = try self.resolve(bin_op.rhs);932 const src_val_id = try self.resolve(bin_op.rhs);
977 const lhs_ty = self.air.typeOf(bin_op.lhs);933 const lhs_ty = self.air.typeOf(bin_op.lhs);
978934
979 const operands = if (lhs_ty.isVolatilePtr())935 const access = spec.MemoryAccess.Extended{
980 &[_]Word{ dst_ptr_id, src_val_id, @bitCast(u32, spec.MemoryAccess{ .Volatile = true }) }936 .Volatile = lhs_ty.isVolatilePtr(),
981 else937 };
982 &[_]Word{ dst_ptr_id, src_val_id };
983938
984 try writeInstruction(&self.code, .OpStore, operands);939 try self.code.emit(self.spv.gpa, .OpStore, .{
940 .pointer = dst_ptr_id,
941 .object = src_val_id,
942 .memory_access = access,
943 });
985 }944 }
986945
987 fn airUnreach(self: *DeclGen) !void {946 fn airUnreach(self: *DeclGen) !void {
988 try writeInstruction(&self.code, .OpUnreachable, &[_]Word{});947 try self.code.emit(self.spv.gpa, .OpUnreachable, {});
989 }948 }
990};949};
src/codegen/spirv/Module.zig created+153
...@@ -0,0 +1,153 @@
1//! This structure represents a SPIR-V (sections) module being compiled, and keeps track of all relevant information.
2//! That includes the actual instructions, the current result-id bound, and data structures for querying result-id's
3//! of data which needs to be persistent over different calls to Decl code generation.
4//!
5//! A SPIR-V binary module supports both little- and big endian layout. The layout is detected by the magic word in the
6//! header. Therefore, we can ignore any byte order throughout the implementation, and just use the host byte order,
7//! and make this a problem for the consumer.
8const Module = @This();
9
10const std = @import("std");
11const Allocator = std.mem.Allocator;
12
13const ZigDecl = @import("../../Module.zig").Decl;
14
15const spec = @import("spec.zig");
16const Word = spec.Word;
17const IdRef = spec.IdRef;
18
19const Section = @import("Section.zig");
20
21/// A general-purpose allocator which may be used to allocate resources for this module
22gpa: Allocator,
23
24/// An arena allocator used to store things that have the same lifetime as this module.
25arena: Allocator,
26
27/// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module".
28sections: struct {
29 /// Capability instructions
30 capabilities: Section = .{},
31 /// OpExtension instructions
32 extensions: Section = .{},
33 // OpExtInstImport instructions - skip for now.
34 // memory model defined by target, not required here.
35 /// OpEntryPoint instructions.
36 entry_points: Section = .{},
37 // OpExecutionMode and OpExecutionModeId instructions - skip for now.
38 /// OpString, OpSourcExtension, OpSource, OpSourceContinued.
39 debug_strings: Section = .{},
40 // OpName, OpMemberName - skip for now.
41 // OpModuleProcessed - skip for now.
42 /// Annotation instructions (OpDecorate etc).
43 annotations: Section = .{},
44 /// Type declarations, constants, global variables
45 /// Below this section, OpLine and OpNoLine is allowed.
46 types_globals_constants: Section = .{},
47 // Functions without a body - skip for now.
48 /// Regular function definitions.
49 functions: Section = .{},
50} = .{},
51
52/// SPIR-V instructions return result-ids. This variable holds the module-wide counter for these.
53next_result_id: Word,
54
55/// Cache for results of OpString instructions for module file names fed to OpSource.
56/// Since OpString is pretty much only used for those, we don't need to keep track of all strings,
57/// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.
58source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
59
60pub fn init(gpa: Allocator, arena: Allocator) Module {
61 return .{
62 .gpa = gpa,
63 .arena = arena,
64 .next_result_id = 1, // 0 is an invalid SPIR-V result id, so start counting at 1.
65 };
66}
67
68pub fn deinit(self: *Module) void {
69 self.sections.capabilities.deinit(self.gpa);
70 self.sections.extensions.deinit(self.gpa);
71 self.sections.entry_points.deinit(self.gpa);
72 self.sections.debug_strings.deinit(self.gpa);
73 self.sections.annotations.deinit(self.gpa);
74 self.sections.types_globals_constants.deinit(self.gpa);
75 self.sections.functions.deinit(self.gpa);
76
77 self.source_file_names.deinit(self.gpa);
78
79 self.* = undefined;
80}
81
82pub fn allocId(self: *Module) spec.IdResult {
83 defer self.next_result_id += 1;
84 return .{.id = self.next_result_id};
85}
86
87pub fn idBound(self: Module) Word {
88 return self.next_result_id;
89}
90
91/// Fetch the result-id of an OpString instruction that encodes the path of the source
92/// file of the decl. This function may also emit an OpSource with source-level information regarding
93/// the decl.
94pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
95 const path = decl.getFileScope().sub_file_path;
96 const result = try self.source_file_names.getOrPut(self.gpa, path);
97 if (!result.found_existing) {
98 const file_result_id = self.allocId();
99 result.value_ptr.* = file_result_id.toRef();
100 try self.sections.debug_strings.emit(self.gpa, .OpString, .{
101 .id_result = file_result_id,
102 .string = path,
103 });
104
105 try self.sections.debug_strings.emit(self.gpa, .OpSource, .{
106 .source_language = .Unknown, // TODO: Register Zig source language.
107 .version = 0, // TODO: Zig version as u32?
108 .file = file_result_id.toRef(),
109 .source = null, // TODO: Store actual source also?
110 });
111 }
112
113 return result.value_ptr.*;
114}
115
116/// Emit this module as a spir-v binary.
117pub fn flush(self: Module, file: std.fs.File) !void {
118 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
119
120 const header = [_]Word{
121 spec.magic_number,
122 (spec.version.major << 16) | (spec.version.minor << 8),
123 0, // TODO: Register Zig compiler magic number.
124 self.idBound(),
125 0, // Schema (currently reserved for future use)
126 };
127
128 // Note: needs to be kept in order according to section 2.3!
129 const buffers = &[_][]const Word{
130 &header,
131 self.sections.capabilities.toWords(),
132 self.sections.extensions.toWords(),
133 self.sections.entry_points.toWords(),
134 self.sections.debug_strings.toWords(),
135 self.sections.annotations.toWords(),
136 self.sections.types_globals_constants.toWords(),
137 self.sections.functions.toWords(),
138 };
139
140 var iovc_buffers: [buffers.len]std.os.iovec_const = undefined;
141 var file_size: u64 = 0;
142 for (iovc_buffers) |*iovc, i| {
143 // Note, since spir-v supports both little and big endian we can ignore byte order here and
144 // just treat the words as a sequence of bytes.
145 const bytes = std.mem.sliceAsBytes(buffers[i]);
146 iovc.* = .{ .iov_base = bytes.ptr, .iov_len = bytes.len };
147 file_size += bytes.len;
148 }
149
150 try file.seekTo(0);
151 try file.setEndPos(file_size);
152 try file.pwritevAll(&iovc_buffers, 0);
153}
src/codegen/spirv/Section.zig+41-9
...@@ -22,17 +22,34 @@ pub fn deinit(section: *Section, allocator: Allocator) void {...@@ -22,17 +22,34 @@ pub fn deinit(section: *Section, allocator: Allocator) void {
22 section.* = undefined;22 section.* = undefined;
23}23}
2424
25fn writeWord(section: *Section, word: Word) void {25/// Clear the instructions in this section
26 section.instructions.appendAssumeCapacity(word);26pub fn reset(section: *Section) void {
27 section.instructions.items.len = 0;
27}28}
2829
29fn writeWords(section: *Section, words: []const Word) void {30pub fn toWords(section: Section) []Word {
30 section.instructions.appendSliceAssumeCapacity(words);31 return section.instructions.items;
31}32}
3233
33// Clear the instructions in this section34/// Append the instructions from another section into this section.
34pub fn reset(section: *Section) void {35pub fn append(
35 section.instructions.items.len = 0;36 section: *Section,
37 allocator: Allocator,
38 other_section: Section
39) !void {
40 try section.instructions.appendSlice(allocator, other_section.instructions.items);
41}
42
43/// Write an instruction and size, operands are to be inserted manually.
44pub fn emitRaw(
45 section: *Section,
46 allocator: Allocator,
47 opcode: Opcode,
48 operands: usize, // opcode itself not included
49) !void {
50 const word_count = 1 + operands;
51 try section.instructions.ensureUnusedCapacity(allocator, word_count);
52 section.writeWord((@intCast(Word, word_count << 16)) | @enumToInt(opcode));
36}53}
3754
38pub fn emit(55pub fn emit(
...@@ -43,10 +60,25 @@ pub fn emit(...@@ -43,10 +60,25 @@ pub fn emit(
43) !void {60) !void {
44 const word_count = instructionSize(opcode, operands);61 const word_count = instructionSize(opcode, operands);
45 try section.instructions.ensureUnusedCapacity(allocator, word_count);62 try section.instructions.ensureUnusedCapacity(allocator, word_count);
46 section.instructions.appendAssumeCapacity(@intCast(Word, word_count << 16) | @enumToInt(opcode));63 section.writeWord(@intCast(Word, word_count << 16) | @enumToInt(opcode));
47 section.writeOperands(opcode.Operands(), operands);64 section.writeOperands(opcode.Operands(), operands);
48}65}
4966
67pub fn writeWord(section: *Section, word: Word) void {
68 section.instructions.appendAssumeCapacity(word);
69}
70
71pub fn writeWords(section: *Section, words: []const Word) void {
72 section.instructions.appendSliceAssumeCapacity(words);
73}
74
75fn writeDoubleWord(section: *Section, dword: DoubleWord) void {
76 section.writeWords(&.{
77 @truncate(Word, dword),
78 @truncate(Word, dword >> @bitSizeOf(Word)),
79 });
80}
81
50fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) void {82fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) void {
51 const fields = switch (@typeInfo(Operands)) {83 const fields = switch (@typeInfo(Operands)) {
52 .Struct => |info| info.fields,84 .Struct => |info| info.fields,
...@@ -59,7 +91,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)...@@ -59,7 +91,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
59 }91 }
60}92}
6193
62fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {94pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
63 switch (Operand) {95 switch (Operand) {
64 spec.IdResultType,96 spec.IdResultType,
65 spec.IdResult,97 spec.IdResult,
src/link/SpirV.zig+37-64
...@@ -32,20 +32,21 @@ const Module = @import("../Module.zig");...@@ -32,20 +32,21 @@ const Module = @import("../Module.zig");
32const Compilation = @import("../Compilation.zig");32const Compilation = @import("../Compilation.zig");
33const link = @import("../link.zig");33const link = @import("../link.zig");
34const codegen = @import("../codegen/spirv.zig");34const codegen = @import("../codegen/spirv.zig");
35const Word = codegen.Word;
36const ResultId = codegen.ResultId;
37const trace = @import("../tracy.zig").trace;35const trace = @import("../tracy.zig").trace;
38const build_options = @import("build_options");36const build_options = @import("build_options");
39const spec = @import("../codegen/spirv/spec.zig");
40const Air = @import("../Air.zig");37const Air = @import("../Air.zig");
41const Liveness = @import("../Liveness.zig");38const Liveness = @import("../Liveness.zig");
42const Value = @import("../value.zig").Value;39const Value = @import("../value.zig").Value;
4340
41const SpvModule = @import("../codegen/spirv/Module.zig");
42const spec = @import("../codegen/spirv/spec.zig");
43const IdResult = spec.IdResult;
44
44// TODO: Should this struct be used at all rather than just a hashmap of aux data for every decl?45// TODO: Should this struct be used at all rather than just a hashmap of aux data for every decl?
45pub const FnData = struct {46pub const FnData = struct {
46 // We're going to fill these in flushModule, and we're going to fill them unconditionally,47 // We're going to fill these in flushModule, and we're going to fill them unconditionally,
47 // so just set it to undefined.48 // so just set it to undefined.
48 id: ResultId = undefined,49 id: IdResult = undefined,
49};50};
5051
51base: link.File,52base: link.File,
...@@ -194,7 +195,10 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {...@@ -194,7 +195,10 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
194 const module = self.base.options.module.?;195 const module = self.base.options.module.?;
195 const target = comp.getTarget();196 const target = comp.getTarget();
196197
197 var spv = codegen.SPIRVModule.init(self.base.allocator, module);198 var arena = std.heap.ArenaAllocator.init(self.base.allocator);
199 defer arena.deinit();
200
201 var spv = SpvModule.init(self.base.allocator, arena.allocator());
198 defer spv.deinit();202 defer spv.deinit();
199203
200 // Allocate an ID for every declaration before generating code,204 // Allocate an ID for every declaration before generating code,
...@@ -202,73 +206,38 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {...@@ -202,73 +206,38 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
202 // TODO: We're allocating an ID unconditionally now, are there206 // TODO: We're allocating an ID unconditionally now, are there
203 // declarations which don't generate a result?207 // declarations which don't generate a result?
204 // TODO: fn_link is used here, but thats probably not the right field. It will work anyway though.208 // TODO: fn_link is used here, but thats probably not the right field. It will work anyway though.
205 {209 for (self.decl_table.keys()) |decl| {
206 for (self.decl_table.keys()) |decl| {210 if (decl.has_tv) {
207 if (!decl.has_tv) continue;211 decl.fn_link.spirv.id = spv.allocId();
208
209 decl.fn_link.spirv.id = spv.allocResultId();
210 }212 }
211 }213 }
212214
213 // Now, actually generate the code for all declarations.215 // Now, actually generate the code for all declarations.
214 {216 var decl_gen = codegen.DeclGen.init(module, &spv);
215 var decl_gen = codegen.DeclGen.init(&spv);217 defer decl_gen.deinit();
216 defer decl_gen.deinit();
217
218 var it = self.decl_table.iterator();
219 while (it.next()) |entry| {
220 const decl = entry.key_ptr.*;
221 if (!decl.has_tv) continue;
222
223 const air = entry.value_ptr.air;
224 const liveness = entry.value_ptr.liveness;
225
226 if (try decl_gen.gen(decl, air, liveness)) |msg| {
227 try module.failed_decls.put(module.gpa, decl, msg);
228 return; // TODO: Attempt to generate more decls?
229 }
230 }
231 }
232218
233 try writeCapabilities(&spv.binary.capabilities_and_extensions, target);219 var it = self.decl_table.iterator();
234 try writeMemoryModel(&spv.binary.capabilities_and_extensions, target);220 while (it.next()) |entry| {
221 const decl = entry.key_ptr.*;
222 if (!decl.has_tv) continue;
235223
236 const header = [_]Word{224 const air = entry.value_ptr.air;
237 spec.magic_number,225 const liveness = entry.value_ptr.liveness;
238 (spec.version.major << 16) | (spec.version.minor << 8),
239 0, // TODO: Register Zig compiler magic number.
240 spv.resultIdBound(),
241 0, // Schema (currently reserved for future use in the SPIR-V spec).
242 };
243
244 // Note: The order of adding sections to the final binary
245 // follows the SPIR-V logical module format!
246 const buffers = &[_][]const Word{
247 &header,
248 spv.binary.capabilities_and_extensions.items,
249 spv.binary.debug_strings.items,
250 spv.binary.types_globals_constants.items,
251 spv.binary.fn_decls.items,
252 };
253226
254 var iovc_buffers: [buffers.len]std.os.iovec_const = undefined;227 // Note, if `decl` is not a function, air/liveness may be undefined.
255 for (iovc_buffers) |*iovc, i| {228 if (try decl_gen.gen(decl, air, liveness)) |msg| {
256 const bytes = std.mem.sliceAsBytes(buffers[i]);229 try module.failed_decls.put(module.gpa, decl, msg);
257 iovc.* = .{ .iov_base = bytes.ptr, .iov_len = bytes.len };230 return; // TODO: Attempt to generate more decls?
231 }
258 }232 }
259233
260 var file_size: u64 = 0;234 try writeCapabilities(&spv, target);
261 for (iovc_buffers) |iov| {235 try writeMemoryModel(&spv, target);
262 file_size += iov.iov_len;
263 }
264236
265 const file = self.base.file.?;237 try spv.flush(self.base.file.?);
266 try file.seekTo(0);
267 try file.setEndPos(file_size);
268 try file.pwritevAll(&iovc_buffers, 0);
269}238}
270239
271fn writeCapabilities(binary: *std.ArrayList(Word), target: std.Target) !void {240fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
272 // TODO: Integrate with a hypothetical feature system241 // TODO: Integrate with a hypothetical feature system
273 const cap: spec.Capability = switch (target.os.tag) {242 const cap: spec.Capability = switch (target.os.tag) {
274 .opencl => .Kernel,243 .opencl => .Kernel,
...@@ -277,10 +246,12 @@ fn writeCapabilities(binary: *std.ArrayList(Word), target: std.Target) !void {...@@ -277,10 +246,12 @@ fn writeCapabilities(binary: *std.ArrayList(Word), target: std.Target) !void {
277 else => unreachable, // TODO246 else => unreachable, // TODO
278 };247 };
279248
280 try codegen.writeInstruction(binary, .OpCapability, &[_]Word{@enumToInt(cap)});249 try spv.sections.capabilities.emit(spv.gpa, .OpCapability, .{
250 .capability = cap,
251 });
281}252}
282253
283fn writeMemoryModel(binary: *std.ArrayList(Word), target: std.Target) !void {254fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void {
284 const addressing_model = switch (target.os.tag) {255 const addressing_model = switch (target.os.tag) {
285 .opencl => switch (target.cpu.arch) {256 .opencl => switch (target.cpu.arch) {
286 .spirv32 => spec.AddressingModel.Physical32,257 .spirv32 => spec.AddressingModel.Physical32,
...@@ -298,8 +269,10 @@ fn writeMemoryModel(binary: *std.ArrayList(Word), target: std.Target) !void {...@@ -298,8 +269,10 @@ fn writeMemoryModel(binary: *std.ArrayList(Word), target: std.Target) !void {
298 else => unreachable,269 else => unreachable,
299 };270 };
300271
301 try codegen.writeInstruction(binary, .OpMemoryModel, &[_]Word{272 // TODO: Put this in a proper section.
302 @enumToInt(addressing_model), @enumToInt(memory_model),273 try spv.sections.capabilities.emit(spv.gpa, .OpMemoryModel, .{
274 .addressing_model = addressing_model,
275 .memory_model = memory_model,
303 });276 });
304}277}
305278