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;
44const log = std.log.scoped(.codegen);
55const assert = std.debug.assert;
66
7const spec = @import("spirv/spec.zig");
8const Opcode = spec.Opcode;
9
107const Module = @import("../Module.zig");
118const Decl = Module.Decl;
129const Type = @import("../type.zig").Type;
......@@ -15,180 +12,75 @@ const LazySrcLoc = Module.LazySrcLoc;
1512const Air = @import("../Air.zig");
1613const Liveness = @import("../Liveness.zig");
1714
18pub const Word = u32;
19pub const ResultId = u32;
15const spec = @import("spirv/spec.zig");
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);
22pub const InstMap = std.AutoHashMap(Air.Inst.Index, ResultId);
25const TypeCache = std.HashMapUnmanaged(Type, IdResultType, Type.HashContext64, std.hash_map.default_max_load_percentage);
26const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
2327
2428const IncomingBlock = struct {
25 src_label_id: ResultId,
26 break_value_id: ResultId,
29 src_label_id: IdRef,
30 break_value_id: IdRef,
2731};
2832
29pub const BlockMap = std.AutoHashMap(Air.Inst.Index, struct {
30 label_id: ResultId,
33pub const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
34 label_id: IdRef,
3135 incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),
3236});
3337
34pub fn writeOpcode(code: *std.ArrayList(Word), opcode: Opcode, arg_count: u16) !void {
35 const word_count: Word = arg_count + 1;
36 try code.append((word_count << 16) | @enumToInt(opcode));
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.
38/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
39pub const DeclGen = struct {
40 /// The Zig module that we are generating decls for.
7641 module: *Module,
7742
78 /// SPIR-V instructions return result-ids. This variable holds the module-wide counter for these.
79 next_result_id: ResultId,
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};
43 /// The SPIR-V module code should be put in.
44 spv: *SpvModule,
15745
158/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
159pub const DeclGen = struct {
160 /// The SPIR-V module code should be put in.
161 spv: *SPIRVModule,
46 /// The decl we are currently generating code for.
47 decl: *Decl,
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!
16351 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!
16455 liveness: Liveness,
16556
16657 /// An array of function argument result-ids. Each index corresponds with the
16758 /// function argument of the same index.
168 args: std.ArrayList(ResultId),
59 args: std.ArrayListUnmanaged(IdRef) = .{},
16960
17061 /// A counter to keep track of how many `arg` instructions we've seen yet.
17162 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
17368 /// A map keeping track of which instruction generated which result-id.
174 inst_results: InstMap,
69 inst_results: InstMap = .{},
17570
17671 /// We need to keep track of result ids for block labels, as well as the 'incoming'
17772 /// blocks for a block.
178 blocks: BlockMap,
73 blocks: BlockMap = .{},
17974
18075 /// The label of the SPIR-V block we are currently generating.
181 current_block_label_id: ResultId,
76 current_block_label_id: IdRef,
18277
18378 /// The actual instructions for this function. We need to declare all locals in
18479 /// the first block, and because we don't know which locals there are going to be,
18580 /// we're just going to generate everything after the locals-section in this array.
18681 /// Note: It will not contain OpFunction, OpFunctionParameter, OpVariable and the
187 /// initial OpLabel. These will be generated into spv.binary.fn_decls directly.
188 code: std.ArrayList(Word),
189
190 /// The decl we are currently generating code for.
191 decl: *Decl,
82 /// initial OpLabel. These will be generated into spv.sections.functions directly.
83 code: SpvSection = .{},
19284
19385 /// If `gen` returned `Error.CodegenFail`, this contains an explanatory message.
19486 /// Memory is owned by `module.gpa`.
......@@ -244,18 +136,15 @@ pub const DeclGen = struct {
244136
245137 /// Initialize the common resources of a DeclGen. Some fields are left uninitialized,
246138 /// only set when `gen` is called.
247 pub fn init(spv: *SPIRVModule) DeclGen {
139 pub fn init(module: *Module, spv: *SpvModule) DeclGen {
248140 return .{
141 .module = module,
249142 .spv = spv,
143 .decl = undefined,
250144 .air = undefined,
251145 .liveness = undefined,
252 .args = std.ArrayList(ResultId).init(spv.gpa),
253146 .next_arg_index = undefined,
254 .inst_results = InstMap.init(spv.gpa),
255 .blocks = BlockMap.init(spv.gpa),
256147 .current_block_label_id = undefined,
257 .code = std.ArrayList(Word).init(spv.gpa),
258 .decl = undefined,
259148 .error_msg = undefined,
260149 };
261150 }
......@@ -265,15 +154,16 @@ pub const DeclGen = struct {
265154 /// returns such a reportable error, it is valid to be called again for a different decl.
266155 pub fn gen(self: *DeclGen, decl: *Decl, air: Air, liveness: Liveness) !?*Module.ErrorMsg {
267156 // Reset internal resources, we don't want to re-allocate these.
157 self.decl = decl;
268158 self.air = air;
269159 self.liveness = liveness;
270160 self.args.items.len = 0;
271161 self.next_arg_index = 0;
162 // Note: don't clear type_cache.
272163 self.inst_results.clearRetainingCapacity();
273164 self.blocks.clearRetainingCapacity();
274165 self.current_block_label_id = undefined;
275 self.code.items.len = 0;
276 self.decl = decl;
166 self.code.reset();
277167 self.error_msg = null;
278168
279169 self.genDecl() catch |err| switch (err) {
......@@ -286,25 +176,38 @@ pub const DeclGen = struct {
286176
287177 /// Free resources owned by the DeclGen.
288178 pub fn deinit(self: *DeclGen) void {
289 self.args.deinit();
290 self.inst_results.deinit();
291 self.blocks.deinit();
292 self.code.deinit();
179 self.args.deinit(self.spv.gpa);
180 self.type_cache.deinit(self.spv.gpa);
181 self.inst_results.deinit(self.spv.gpa);
182 self.blocks.deinit(self.spv.gpa);
183 self.code.deinit(self.spv.gpa);
293184 }
294185
186 /// Return the target which we are currently compiling for.
295187 fn getTarget(self: *DeclGen) std.Target {
296 return self.spv.module.getTarget();
188 return self.module.getTarget();
297189 }
298190
299191 fn fail(self: *DeclGen, comptime format: []const u8, args: anytype) Error {
300192 @setCold(true);
301193 const src: LazySrcLoc = .{ .node_offset = 0 };
302194 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);
304206 return error.CodegenFail;
305207 }
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 {
308211 if (self.air.value(inst)) |val| {
309212 return self.genConstant(self.air.typeOf(inst), val);
310213 }
......@@ -312,9 +215,13 @@ pub const DeclGen = struct {
312215 return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
313216 }
314217
315 fn beginSPIRVBlock(self: *DeclGen, label_id: ResultId) !void {
316 try writeInstruction(&self.code, .OpLabel, &[_]Word{label_id});
317 self.current_block_label_id = label_id;
218 /// Start a new SPIR-V block, Emits the label of the new block, and stores which
219 /// block we are currently generating.
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();
318225 }
319226
320227 /// 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 {
396303 const int_info = ty.intInfo(target);
397304 // TODO: Maybe it's useful to also return this value.
398305 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|
400 if (backing_bits == int_info.bits)
401 ArithmeticTypeInfo.Class.integer
306 break :blk ArithmeticTypeInfo{
307 .bits = int_info.bits,
308 .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
402315 else
403 ArithmeticTypeInfo.Class.strange_integer
404 else
405 .composite_integer };
316 .composite_integer,
317 };
406318 },
407319 // As of yet, there is no vector support in the self-hosted compiler.
408320 .Vector => self.fail("TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}),
......@@ -413,15 +325,15 @@ pub const DeclGen = struct {
413325
414326 /// Generate a constant representing `val`.
415327 /// TODO: Deduplication?
416 fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!ResultId {
328 fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!IdRef {
417329 const target = self.getTarget();
418 const code = &self.spv.binary.types_globals_constants;
419 const result_id = self.spv.allocResultId();
330 const section = &self.spv.sections.types_globals_constants;
331 const result_id = self.spv.allocId();
420332 const result_type_id = try self.genType(ty);
421333
422334 if (val.isUndef()) {
423 try writeInstruction(code, .OpUndef, &[_]Word{ result_type_id, result_id });
424 return result_id;
335 try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_type_id, .id_result = result_id });
336 return result_id.toRef();
425337 }
426338
427339 switch (ty.zigTypeTag()) {
......@@ -436,76 +348,71 @@ pub const DeclGen = struct {
436348 // SPIR-V native type (up to i/u64 with Int64). If SPIR-V ever supports native ints of a larger size, this
437349 // might need to be updated.
438350 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
439354 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 values
442 // only use the actual bits of the type.
443 // TODO: Should this be the backing type bits or the actual type bits?
444 int_bits &= (@as(u64, 1) << @intCast(u6, backing_bits)) - 1;
445
446 switch (backing_bits) {
447 0 => unreachable,
448 1...32 => try writeInstruction(code, .OpConstant, &[_]Word{
449 result_type_id,
450 result_id,
451 @truncate(u32, int_bits),
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 }
356 const value: spec.LiteralContextDependentNumber = switch (backing_bits) {
357 1...32 => .{.uint32 = @truncate(u32, int_bits)},
358 33...64 => .{.uint64 = int_bits},
359 else => unreachable,
360 };
361
362 try section.emit(self.spv.gpa, .OpConstant, .{
363 .id_result_type = result_type_id,
364 .id_result = result_id,
365 .value = value,
366 });
461367 },
462368 .Bool => {
463 const opcode: Opcode = if (val.toBool()) .OpConstantTrue else .OpConstantFalse;
464 try writeInstruction(code, opcode, &[_]Word{ result_type_id, result_id });
369 const operands = .{ .id_result_type = result_type_id, .id_result = 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 }
465375 },
466376 .Float => {
467377 // At this point we are guaranteed that the target floating point type is supported, otherwise the function
468378 // would have exited at genType(ty).
469379
470 // f16 and f32 require one word of storage. f64 requires 2, low-order first.
471
472 switch (ty.floatBits(target)) {
473 16 => try writeInstruction(code, .OpConstant, &[_]Word{ result_type_id, result_id, @bitCast(u16, val.toFloat(f16)) }),
474 32 => try writeInstruction(code, .OpConstant, &[_]Word{ result_type_id, result_id, @bitCast(u32, val.toFloat(f32)) }),
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 },
380 const value: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) {
381 // Prevent upcasting to f32 by bitcasting and writing as a uint32.
382 16 => .{.uint32 = @bitCast(u16, val.toFloat(f16))},
383 32 => .{.float32 = val.toFloat(f32)},
384 64 => .{.float64 = val.toFloat(f64)},
484385 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?
486387 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 });
488395 },
489396 .Void => unreachable,
490397 else => return self.fail("TODO: SPIR-V backend: constant generation of type {}", .{ty}),
491398 }
492399
493 return result_id;
400 return result_id.toRef();
494401 }
495402
496 fn genType(self: *DeclGen, ty: Type) Error!ResultId {
403 fn genType(self: *DeclGen, ty: Type) Error!IdResultType {
497404 // 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| {
499406 return already_generated;
500407 }
501408
502409 const target = self.getTarget();
503 const code = &self.spv.binary.types_globals_constants;
504 const result_id = self.spv.allocResultId();
410 const section = &self.spv.sections.types_globals_constants;
411 const result_id = self.spv.allocId();
505412
506413 switch (ty.zigTypeTag()) {
507 .Void => try writeInstruction(code, .OpTypeVoid, &[_]Word{result_id}),
508 .Bool => try writeInstruction(code, .OpTypeBool, &[_]Word{result_id}),
414 .Void => try section.emit(self.spv.gpa, .OpTypeVoid, .{.id_result = result_id}),
415 .Bool => try section.emit(self.spv.gpa, .OpTypeBool, .{.id_result = result_id}),
509416 .Int => {
510417 const int_info = ty.intInfo(target);
511418 const backing_bits = self.backingIntBits(int_info.bits) orelse {
......@@ -514,11 +421,11 @@ pub const DeclGen = struct {
514421 };
515422
516423 // TODO: If backing_bits != int_info.bits, a duplicate type might be generated here.
517 try writeInstruction(code, .OpTypeInt, &[_]Word{
518 result_id,
519 backing_bits,
520 switch (int_info.signedness) {
521 .unsigned => 0,
424 try section.emit(self.spv.gpa, .OpTypeInt, .{
425 .id_result = result_id,
426 .width = backing_bits,
427 .signedness = switch (int_info.signedness) {
428 .unsigned => @as(spec.LiteralInteger, 0),
522429 .signed => 1,
523430 },
524431 });
......@@ -539,7 +446,7 @@ pub const DeclGen = struct {
539446 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
540447 }
541448
542 try writeInstruction(code, .OpTypeFloat, &[_]Word{ result_id, bits });
449 try section.emit(self.spv.gpa, .OpTypeFloat, .{.id_result = result_id, .width = bits});
543450 },
544451 .Fn => {
545452 // We only support zig-calling-convention functions, no varargs.
......@@ -558,14 +465,16 @@ pub const DeclGen = struct {
558465
559466 const return_type_id = try self.genType(ty.fnReturnType());
560467
468 try section.emitRaw(self.spv.gpa, .OpTypeFunction, 2 + @intCast(u16, ty.fnParamLen()));
469
561470 // result id + result type id + parameter type ids.
562 try writeOpcode(code, .OpTypeFunction, 2 + @intCast(u16, ty.fnParamLen()));
563 try code.appendSlice(&.{ result_id, return_type_id });
471 section.writeOperand(IdResult, result_id);
472 section.writeOperand(IdResultType, return_type_id);
564473
565474 i = 0;
566475 while (i < params) : (i += 1) {
567 const param_type_id = self.spv.types.get(ty.fnParamType(i)).?;
568 try code.append(param_type_id);
476 const param_type_id = self.type_cache.get(ty.fnParamType(i)).?;
477 section.writeOperand(IdRef, param_type_id.toRef());
569478 }
570479 },
571480 // When recursively generating a type, we cannot infer the pointer's storage class. See genPointerType.
......@@ -594,26 +503,29 @@ pub const DeclGen = struct {
594503 else => |tag| return self.fail("TODO: SPIR-V backend: implement type {}s", .{tag}),
595504 }
596505
597 try self.spv.types.putNoClobber(ty, result_id);
598 return result_id;
506 try self.type_cache.putNoClobber(self.spv.gpa, ty, result_id.toResultType());
507 return result_id.toResultType();
599508 }
600509
601510 /// SPIR-V requires pointers to have a storage class (address space), and so we have a special function for that.
602511 /// 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 {
604513 assert(ty.zigTypeTag() == .Pointer);
605514
606 const code = &self.spv.binary.types_globals_constants;
607 const result_id = self.spv.allocResultId();
515 const result_id = self.spv.allocId();
608516
609517 // TODO: There are many constraints which are ignored for now: We may only create pointers to certain types, and to other types
610518 // if more capabilities are enabled. For example, we may only create pointers to f16 if Float16Buffer is enabled.
611519 // These also relates to the pointer's address space.
612520 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();
617529 }
618530
619531 fn genDecl(self: *DeclGen) !void {
......@@ -623,38 +535,43 @@ pub const DeclGen = struct {
623535 if (decl.val.castTag(.function)) |_| {
624536 assert(decl.ty.zigTypeTag() == .Fn);
625537 const prototype_id = try self.genType(decl.ty);
626 try writeInstruction(&self.spv.binary.fn_decls, .OpFunction, &[_]Word{
627 self.spv.types.get(decl.ty.fnReturnType()).?, // This type should be generated along with the prototype.
628 result_id,
629 @bitCast(Word, spec.FunctionControl{}), // TODO: We can set inline here if the type requires it.
630 prototype_id,
538 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunction, .{
539 .id_result_type = self.type_cache.get(decl.ty.fnReturnType()).?, // This type should be generated along with the prototype.
540 .id_result = result_id,
541 .function_control = .{}, // TODO: We can set inline here if the type requires it.
542 .function_type = prototype_id.toRef(),
631543 });
632544
633545 const params = decl.ty.fnParamLen();
634546 var i: usize = 0;
635547
636 try self.args.ensureUnusedCapacity(params);
548 try self.args.ensureUnusedCapacity(self.spv.gpa, params);
637549 while (i < params) : (i += 1) {
638 const param_type_id = self.spv.types.get(decl.ty.fnParamType(i)).?;
639 const arg_result_id = self.spv.allocResultId();
640 try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionParameter, &[_]Word{ param_type_id, arg_result_id });
641 self.args.appendAssumeCapacity(arg_result_id);
550 const param_type_id = self.type_cache.get(decl.ty.fnParamType(i)).?;
551 const arg_result_id = self.spv.allocId();
552 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionParameter, .{
553 .id_result_type = param_type_id,
554 .id_result = arg_result_id,
555 });
556 self.args.appendAssumeCapacity(arg_result_id.toRef());
642557 }
643558
644559 // 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 after
648 // here. Since we're not generating in self.code, we're just going to bypass self.beginSPIRVBlock here.
649 try writeInstruction(&self.spv.binary.fn_decls, .OpLabel, &[_]Word{root_block_id});
650 self.current_block_label_id = root_block_id;
562 // We need to generate the label directly in the functions section here because we're going to write the local variables after
563 // here. Since we're not generating in self.code, we're just going to bypass self.beginSpvBlock here.
564 try self.spv.sections.functions.emit(self.spv.gpa, .OpLabel, .{
565 .id_result = root_block_id,
566 });
567 self.current_block_label_id = root_block_id.toRef();
651568
652569 const main_body = self.air.getMainBody();
653570 try self.genBody(main_body);
654571
655 // Append the actual code into the fn_decls section.
656 try self.spv.binary.fn_decls.appendSlice(self.code.items);
657 try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionEnd, &[_]Word{});
572 // Append the actual code into the functions section.
573 try self.spv.sections.functions.append(self.spv.gpa, self.code);
574 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionEnd, {});
658575 } else {
659576 return self.fail("TODO: SPIR-V backend: generate decl type {}", .{decl.ty.zigTypeTag()});
660577 }
......@@ -670,9 +587,9 @@ pub const DeclGen = struct {
670587 const air_tags = self.air.instructions.items(.tag);
671588 const result_id = switch (air_tags[inst]) {
672589 // zig fmt: off
673 .add, .addwrap => try self.airArithOp(inst, .{.OpFAdd, .OpIAdd, .OpIAdd}),
674 .sub, .subwrap => try self.airArithOp(inst, .{.OpFSub, .OpISub, .OpISub}),
675 .mul, .mulwrap => try self.airArithOp(inst, .{.OpFMul, .OpIMul, .OpIMul}),
590 .add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd),
591 .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub),
592 .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul),
676593
677594 .bit_and => try self.airBinOpSimple(inst, .OpBitwiseAnd),
678595 .bit_or => try self.airBinOpSimple(inst, .OpBitwiseOr),
......@@ -682,12 +599,12 @@ pub const DeclGen = struct {
682599
683600 .not => try self.airNot(inst),
684601
685 .cmp_eq => try self.airCmp(inst, .{.OpFOrdEqual, .OpLogicalEqual, .OpIEqual}),
686 .cmp_neq => try self.airCmp(inst, .{.OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual}),
687 .cmp_gt => try self.airCmp(inst, .{.OpFOrdGreaterThan, .OpSGreaterThan, .OpUGreaterThan}),
688 .cmp_gte => try self.airCmp(inst, .{.OpFOrdGreaterThanEqual, .OpSGreaterThanEqual, .OpUGreaterThanEqual}),
689 .cmp_lt => try self.airCmp(inst, .{.OpFOrdLessThan, .OpSLessThan, .OpULessThan}),
690 .cmp_lte => try self.airCmp(inst, .{.OpFOrdLessThanEqual, .OpSLessThanEqual, .OpULessThanEqual}),
602 .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual),
603 .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual),
604 .cmp_gt => try self.airCmp(inst, .OpFOrdGreaterThan, .OpSGreaterThan, .OpUGreaterThan),
605 .cmp_gte => try self.airCmp(inst, .OpFOrdGreaterThanEqual, .OpSGreaterThanEqual, .OpUGreaterThanEqual),
606 .cmp_lt => try self.airCmp(inst, .OpFOrdLessThan, .OpSLessThan, .OpULessThan),
607 .cmp_lte => try self.airCmp(inst, .OpFOrdLessThanEqual, .OpSLessThanEqual, .OpULessThanEqual),
691608
692609 .arg => self.airArg(),
693610 .alloc => try self.airAlloc(inst),
......@@ -710,22 +627,25 @@ pub const DeclGen = struct {
710627 }),
711628 };
712629
713 try self.inst_results.putNoClobber(inst, result_id);
630 try self.inst_results.putNoClobber(self.spv.gpa, inst, result_id);
714631 }
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 {
717634 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
718635 const lhs_id = try self.resolve(bin_op.lhs);
719636 const rhs_id = try self.resolve(bin_op.rhs);
720 const result_id = self.spv.allocResultId();
637 const result_id = self.spv.allocId();
721638 const result_type_id = try self.genType(self.air.typeOfIndex(inst));
722 try writeInstruction(&self.code, opcode, &[_]Word{
723 result_type_id, result_id, lhs_id, rhs_id,
639 try self.code.emit(self.spv.gpa, opcode, .{
640 .id_result_type = result_type_id,
641 .id_result = result_id,
642 .operand_1 = lhs_id,
643 .operand_2 = rhs_id,
724644 });
725 return result_id;
645 return result_id.toRef();
726646 }
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 {
729649 // LHS and RHS are guaranteed to have the same type, and AIR guarantees
730650 // the result to be the same as the LHS and RHS, which matches SPIR-V.
731651 const ty = self.air.typeOfIndex(inst);
......@@ -733,7 +653,7 @@ pub const DeclGen = struct {
733653 const lhs_id = try self.resolve(bin_op.lhs);
734654 const rhs_id = try self.resolve(bin_op.rhs);
735655
736 const result_id = self.spv.allocResultId();
656 const result_id = self.spv.allocId();
737657 const result_type_id = try self.genType(ty);
738658
739659 assert(self.air.typeOf(bin_op.lhs).eql(ty));
......@@ -757,20 +677,31 @@ pub const DeclGen = struct {
757677 .float => 0,
758678 else => unreachable,
759679 };
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 }
763694 // TODO: Trap on overflow? Probably going to be annoying.
764695 // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap.
765696
766 return result_id;
697 return result_id.toRef();
767698 }
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 {
770701 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
771702 const lhs_id = try self.resolve(bin_op.lhs);
772703 const rhs_id = try self.resolve(bin_op.rhs);
773 const result_id = self.spv.allocResultId();
704 const result_id = self.spv.allocId();
774705 const result_type_id = try self.genType(Type.initTag(.bool));
775706 const op_ty = self.air.typeOf(bin_op.lhs);
776707 assert(op_ty.eql(self.air.typeOf(bin_op.rhs)));
......@@ -793,53 +724,71 @@ pub const DeclGen = struct {
793724 .unsigned => @as(usize, 2),
794725 },
795726 };
796 const opcode = ops[opcode_index];
797727
798 try writeInstruction(&self.code, opcode, &[_]Word{ result_type_id, result_id, lhs_id, rhs_id });
799 return result_id;
728 const operands = .{
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();
800743 }
801744
802 fn airNot(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
745 fn airNot(self: *DeclGen, inst: Air.Inst.Index) !IdRef {
803746 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
804747 const operand_id = try self.resolve(ty_op.operand);
805 const result_id = self.spv.allocResultId();
748 const result_id = self.spv.allocId();
806749 const result_type_id = try self.genType(Type.initTag(.bool));
807 const opcode: Opcode = .OpLogicalNot;
808 try writeInstruction(&self.code, opcode, &[_]Word{ result_type_id, result_id, operand_id });
809 return result_id;
750 try self.code.emit(self.spv.gpa, .OpLogicalNot, .{
751 .id_result_type = result_type_id,
752 .id_result = result_id,
753 .operand = operand_id,
754 });
755 return result_id.toRef();
810756 }
811757
812 fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
758 fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !IdRef {
813759 const ty = self.air.typeOfIndex(inst);
814760 const storage_class = spec.StorageClass.Function;
815761 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 that
764 // Rather than generating into code here, we're just going to generate directly into the functions section so that
819765 // 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) });
821
822 return result_id;
766 try self.spv.sections.functions.emit(self.spv.gpa, .OpVariable, .{
767 .id_result_type = result_type_id,
768 .id_result = result_id,
769 .storage_class = storage_class,
770 });
771 return result_id.toRef();
823772 }
824773
825 fn airArg(self: *DeclGen) ResultId {
774 fn airArg(self: *DeclGen) IdRef {
826775 defer self.next_arg_index += 1;
827776 return self.args.items[self.next_arg_index];
828777 }
829778
830 fn airBlock(self: *DeclGen, inst: Air.Inst.Index) !?ResultId {
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 and
779 fn airBlock(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
780 // 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
832781 // "return" a value from. This cannot be directly modelled in SPIR-V, so in a block instruction, we're going to split up
833782 // the current block by first generating the code of the block, then a label, and then generate the rest of the current
834783 // ir.Block in a different SPIR-V block.
835784
836 const label_id = self.spv.allocResultId();
785 const label_id = self.spv.allocId();
837786
838787 // 4 chosen as arbitrary initial capacity.
839788 var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.spv.gpa, 4);
840789
841 try self.blocks.putNoClobber(inst, .{
842 .label_id = label_id,
790 try self.blocks.putNoClobber(self.spv.gpa, inst, .{
791 .label_id = label_id.toRef(),
843792 .incoming_blocks = &incoming_blocks,
844793 });
845794 defer {
......@@ -853,7 +802,7 @@ pub const DeclGen = struct {
853802 const body = self.air.extra[extra.end..][0..extra.data.body_len];
854803
855804 try self.genBody(body);
856 try self.beginSPIRVBlock(label_id);
805 try self.beginSpvBlock(label_id);
857806
858807 // If this block didn't produce a value, simply return here.
859808 if (!ty.hasRuntimeBits())
......@@ -861,7 +810,7 @@ pub const DeclGen = struct {
861810
862811 // 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
866815 // TODO: OpPhi is limited in the types that it may produce, such as pointers. Figure out which other types
867816 // 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 {
869818 const result_type_id = try self.genType(ty);
870819 _ = 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
874823 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 });
876825 }
877826
878 return result_id;
827 return result_id.toRef();
879828 }
880829
881830 fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void {
......@@ -889,7 +838,7 @@ pub const DeclGen = struct {
889838 try block.incoming_blocks.append(self.spv.gpa, .{ .src_label_id = self.current_block_label_id, .break_value_id = operand_id });
890839 }
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});
893842 }
894843
895844 fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {
......@@ -900,63 +849,70 @@ pub const DeclGen = struct {
900849 const condition_id = try self.resolve(pl_op.operand);
901850
902851 // 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();
904 const else_label_id = self.spv.allocResultId();
852 const then_label_id = self.spv.allocId();
853 const else_label_id = self.spv.allocId();
905854
906855 // TODO: We can generate OpSelectionMerge here if we know the target block that both of these will resolve to,
907856 // but i don't know if those will always resolve to the same block.
908857
909 try writeInstruction(&self.code, .OpBranchConditional, &[_]Word{
910 condition_id,
911 then_label_id,
912 else_label_id,
858 try self.code.emit(self.spv.gpa, .OpBranchConditional, .{
859 .condition = condition_id,
860 .true_label = then_label_id.toRef(),
861 .false_label = else_label_id.toRef(),
913862 });
914863
915 try self.beginSPIRVBlock(then_label_id);
864 try self.beginSpvBlock(then_label_id);
916865 try self.genBody(then_body);
917 try self.beginSPIRVBlock(else_label_id);
866 try self.beginSpvBlock(else_label_id);
918867 try self.genBody(else_body);
919868 }
920869
921870 fn airDbgStmt(self: *DeclGen, inst: Air.Inst.Index) !void {
922871 const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
923872 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 });
925878 }
926879
927 fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
880 fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !IdRef {
928881 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
929882 const operand_id = try self.resolve(ty_op.operand);
930883 const ty = self.air.typeOfIndex(inst);
931884
932885 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())
936 &[_]Word{ result_type_id, result_id, operand_id, @bitCast(u32, spec.MemoryAccess{ .Volatile = true }) }
937 else
938 &[_]Word{ result_type_id, result_id, operand_id };
888 const access = spec.MemoryAccess.Extended{
889 .Volatile = ty.isVolatilePtr(),
890 };
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();
943900 }
944901
945902 fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void {
946903 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
947904 const loop = self.air.extraData(Air.Block, ty_pl.payload);
948905 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
951908 // 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
954911 // TODO: Look into OpLoopMerge.
955
956 try self.beginSPIRVBlock(loop_label_id);
912 try self.beginSpvBlock(loop_label_id);
957913 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()});
960916 }
961917
962918 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {
......@@ -964,9 +920,9 @@ pub const DeclGen = struct {
964920 const operand_ty = self.air.typeOf(operand);
965921 if (operand_ty.hasRuntimeBits()) {
966922 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});
968924 } else {
969 try writeInstruction(&self.code, .OpReturn, &[_]Word{});
925 try self.code.emit(self.spv.gpa, .OpReturn, {});
970926 }
971927 }
972928
......@@ -976,15 +932,18 @@ pub const DeclGen = struct {
976932 const src_val_id = try self.resolve(bin_op.rhs);
977933 const lhs_ty = self.air.typeOf(bin_op.lhs);
978934
979 const operands = if (lhs_ty.isVolatilePtr())
980 &[_]Word{ dst_ptr_id, src_val_id, @bitCast(u32, spec.MemoryAccess{ .Volatile = true }) }
981 else
982 &[_]Word{ dst_ptr_id, src_val_id };
935 const access = spec.MemoryAccess.Extended{
936 .Volatile = lhs_ty.isVolatilePtr(),
937 };
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 });
985944 }
986945
987946 fn airUnreach(self: *DeclGen) !void {
988 try writeInstruction(&self.code, .OpUnreachable, &[_]Word{});
947 try self.code.emit(self.spv.gpa, .OpUnreachable, {});
989948 }
990949};
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 {
2222 section.* = undefined;
2323}
2424
25fn writeWord(section: *Section, word: Word) void {
26 section.instructions.appendAssumeCapacity(word);
25/// Clear the instructions in this section
26pub fn reset(section: *Section) void {
27 section.instructions.items.len = 0;
2728}
2829
29fn writeWords(section: *Section, words: []const Word) void {
30 section.instructions.appendSliceAssumeCapacity(words);
30pub fn toWords(section: Section) []Word {
31 return section.instructions.items;
3132}
3233
33// Clear the instructions in this section
34pub fn reset(section: *Section) void {
35 section.instructions.items.len = 0;
34/// Append the instructions from another section into this section.
35pub fn append(
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));
3653}
3754
3855pub fn emit(
......@@ -43,10 +60,25 @@ pub fn emit(
4360) !void {
4461 const word_count = instructionSize(opcode, operands);
4562 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));
4764 section.writeOperands(opcode.Operands(), operands);
4865}
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
5082fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) void {
5183 const fields = switch (@typeInfo(Operands)) {
5284 .Struct => |info| info.fields,
......@@ -59,7 +91,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
5991 }
6092}
6193
62fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
94pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
6395 switch (Operand) {
6496 spec.IdResultType,
6597 spec.IdResult,
src/link/SpirV.zig+37-64
......@@ -32,20 +32,21 @@ const Module = @import("../Module.zig");
3232const Compilation = @import("../Compilation.zig");
3333const link = @import("../link.zig");
3434const codegen = @import("../codegen/spirv.zig");
35const Word = codegen.Word;
36const ResultId = codegen.ResultId;
3735const trace = @import("../tracy.zig").trace;
3836const build_options = @import("build_options");
39const spec = @import("../codegen/spirv/spec.zig");
4037const Air = @import("../Air.zig");
4138const Liveness = @import("../Liveness.zig");
4239const 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
4445// TODO: Should this struct be used at all rather than just a hashmap of aux data for every decl?
4546pub const FnData = struct {
4647 // We're going to fill these in flushModule, and we're going to fill them unconditionally,
4748 // so just set it to undefined.
48 id: ResultId = undefined,
49 id: IdResult = undefined,
4950};
5051
5152base: link.File,
......@@ -194,7 +195,10 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
194195 const module = self.base.options.module.?;
195196 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());
198202 defer spv.deinit();
199203
200204 // Allocate an ID for every declaration before generating code,
......@@ -202,73 +206,38 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
202206 // TODO: We're allocating an ID unconditionally now, are there
203207 // declarations which don't generate a result?
204208 // TODO: fn_link is used here, but thats probably not the right field. It will work anyway though.
205 {
206 for (self.decl_table.keys()) |decl| {
207 if (!decl.has_tv) continue;
208
209 decl.fn_link.spirv.id = spv.allocResultId();
209 for (self.decl_table.keys()) |decl| {
210 if (decl.has_tv) {
211 decl.fn_link.spirv.id = spv.allocId();
210212 }
211213 }
212214
213215 // Now, actually generate the code for all declarations.
214 {
215 var decl_gen = codegen.DeclGen.init(&spv);
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 }
216 var decl_gen = codegen.DeclGen.init(module, &spv);
217 defer decl_gen.deinit();
232218
233 try writeCapabilities(&spv.binary.capabilities_and_extensions, target);
234 try writeMemoryModel(&spv.binary.capabilities_and_extensions, target);
219 var it = self.decl_table.iterator();
220 while (it.next()) |entry| {
221 const decl = entry.key_ptr.*;
222 if (!decl.has_tv) continue;
235223
236 const header = [_]Word{
237 spec.magic_number,
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 };
224 const air = entry.value_ptr.air;
225 const liveness = entry.value_ptr.liveness;
253226
254 var iovc_buffers: [buffers.len]std.os.iovec_const = undefined;
255 for (iovc_buffers) |*iovc, i| {
256 const bytes = std.mem.sliceAsBytes(buffers[i]);
257 iovc.* = .{ .iov_base = bytes.ptr, .iov_len = bytes.len };
227 // Note, if `decl` is not a function, air/liveness may be undefined.
228 if (try decl_gen.gen(decl, air, liveness)) |msg| {
229 try module.failed_decls.put(module.gpa, decl, msg);
230 return; // TODO: Attempt to generate more decls?
231 }
258232 }
259233
260 var file_size: u64 = 0;
261 for (iovc_buffers) |iov| {
262 file_size += iov.iov_len;
263 }
234 try writeCapabilities(&spv, target);
235 try writeMemoryModel(&spv, target);
264236
265 const file = self.base.file.?;
266 try file.seekTo(0);
267 try file.setEndPos(file_size);
268 try file.pwritevAll(&iovc_buffers, 0);
237 try spv.flush(self.base.file.?);
269238}
270239
271fn writeCapabilities(binary: *std.ArrayList(Word), target: std.Target) !void {
240fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
272241 // TODO: Integrate with a hypothetical feature system
273242 const cap: spec.Capability = switch (target.os.tag) {
274243 .opencl => .Kernel,
......@@ -277,10 +246,12 @@ fn writeCapabilities(binary: *std.ArrayList(Word), target: std.Target) !void {
277246 else => unreachable, // TODO
278247 };
279248
280 try codegen.writeInstruction(binary, .OpCapability, &[_]Word{@enumToInt(cap)});
249 try spv.sections.capabilities.emit(spv.gpa, .OpCapability, .{
250 .capability = cap,
251 });
281252}
282253
283fn writeMemoryModel(binary: *std.ArrayList(Word), target: std.Target) !void {
254fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void {
284255 const addressing_model = switch (target.os.tag) {
285256 .opencl => switch (target.cpu.arch) {
286257 .spirv32 => spec.AddressingModel.Physical32,
......@@ -298,8 +269,10 @@ fn writeMemoryModel(binary: *std.ArrayList(Word), target: std.Target) !void {
298269 else => unreachable,
299270 };
300271
301 try codegen.writeInstruction(binary, .OpMemoryModel, &[_]Word{
302 @enumToInt(addressing_model), @enumToInt(memory_model),
272 // TODO: Put this in a proper section.
273 try spv.sections.capabilities.emit(spv.gpa, .OpMemoryModel, .{
274 .addressing_model = addressing_model,
275 .memory_model = memory_model,
303276 });
304277}
305278