authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-18 13:31:22+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-22 16:11:56+02:00
logc190b2ff83308a6680b9d4587d742c253dcdee5d
tree1a412788fafb029f010c9f5a4f80cacf9eb57503
parent9ddd7f4a60c70c1bf146c2fd0c35b32098755f77

SPIR-V: ResultId and Word aliases to improve code clarity


2 files changed, 65 insertions(+), 75 deletions(-)

src/codegen/spirv.zig+51-64
......@@ -14,16 +14,19 @@ const LazySrcLoc = Module.LazySrcLoc;
1414const ir = @import("../ir.zig");
1515const Inst = ir.Inst;
1616
17pub const TypeMap = std.HashMap(Type, u32, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
18pub const InstMap = std.AutoHashMap(*Inst, u32);
17pub const Word = u32;
18pub const ResultId = u32;
1919
20pub fn writeOpcode(code: *std.ArrayList(u32), opcode: Opcode, arg_count: u32) !void {
21 const word_count = arg_count + 1;
20pub const TypeMap = std.HashMap(Type, ResultId, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
21pub const InstMap = std.AutoHashMap(*Inst, ResultId);
22
23pub fn writeOpcode(code: *std.ArrayList(Word), opcode: Opcode, arg_count: u16) !void {
24 const word_count: Word = arg_count + 1;
2225 try code.append((word_count << 16) | @enumToInt(opcode));
2326}
2427
25pub fn writeInstruction(code: *std.ArrayList(u32), opcode: Opcode, args: []const u32) !void {
26 try writeOpcode(code, opcode, @intCast(u32, args.len));
28pub fn writeInstruction(code: *std.ArrayList(Word), opcode: Opcode, args: []const Word) !void {
29 try writeOpcode(code, opcode, @intCast(u16, args.len));
2730 try code.appendSlice(args);
2831}
2932
......@@ -31,11 +34,11 @@ pub fn writeInstruction(code: *std.ArrayList(u32), opcode: Opcode, args: []const
3134/// That includes the actual instructions, the current result-id bound, and data structures for querying result-id's
3235/// of data which needs to be persistent over different calls to Decl code generation.
3336pub const SPIRVModule = struct {
34 next_result_id: u32,
37 next_result_id: ResultId,
3538
3639 binary: struct {
37 types_globals_constants: std.ArrayList(u32),
38 fn_decls: std.ArrayList(u32),
40 types_globals_constants: std.ArrayList(Word),
41 fn_decls: std.ArrayList(Word),
3942 },
4043
4144 types: TypeMap,
......@@ -44,8 +47,8 @@ pub const SPIRVModule = struct {
4447 return .{
4548 .next_result_id = 1, // 0 is an invalid SPIR-V result ID.
4649 .binary = .{
47 .types_globals_constants = std.ArrayList(u32).init(gpa),
48 .fn_decls = std.ArrayList(u32).init(gpa),
50 .types_globals_constants = std.ArrayList(Word).init(gpa),
51 .fn_decls = std.ArrayList(Word).init(gpa),
4952 },
5053 .types = TypeMap.init(gpa),
5154 };
......@@ -57,12 +60,12 @@ pub const SPIRVModule = struct {
5760 self.types.deinit();
5861 }
5962
60 pub fn allocResultId(self: *SPIRVModule) u32 {
63 pub fn allocResultId(self: *SPIRVModule) Word {
6164 defer self.next_result_id += 1;
6265 return self.next_result_id;
6366 }
6467
65 pub fn resultIdBound(self: *SPIRVModule) u32 {
68 pub fn resultIdBound(self: *SPIRVModule) Word {
6669 return self.next_result_id;
6770 }
6871};
......@@ -76,7 +79,7 @@ pub const DeclGen = struct {
7679 spv: *SPIRVModule,
7780
7881 /// An array of function argument result-ids. Each index corresponds with the function argument of the same index.
79 args: std.ArrayList(u32),
82 args: std.ArrayList(ResultId),
8083
8184 /// A counter to keep track of how many `arg` instructions we've seen yet.
8285 next_arg_index: u32,
......@@ -145,7 +148,7 @@ pub const DeclGen = struct {
145148 return error.AnalysisFail;
146149 }
147150
148 fn resolve(self: *DeclGen, inst: *Inst) !u32 {
151 fn resolve(self: *DeclGen, inst: *Inst) !ResultId {
149152 if (inst.value()) |val| {
150153 return self.genConstant(inst.ty, val);
151154 }
......@@ -249,21 +252,21 @@ pub const DeclGen = struct {
249252
250253 /// Generate a constant representing `val`.
251254 /// TODO: Deduplication?
252 fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!u32 {
255 fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!ResultId {
253256 const target = self.module.getTarget();
254257 const code = &self.spv.binary.types_globals_constants;
255258 const result_id = self.spv.allocResultId();
256259 const result_type_id = try self.getOrGenType(ty);
257260
258261 if (val.isUndef()) {
259 try writeInstruction(code, .OpUndef, &[_]u32{ result_type_id, result_id });
262 try writeInstruction(code, .OpUndef, &[_]Word{ result_type_id, result_id });
260263 return result_id;
261264 }
262265
263266 switch (ty.zigTypeTag()) {
264267 .Bool => {
265268 const opcode: Opcode = if (val.toBool()) .OpConstantTrue else .OpConstantFalse;
266 try writeInstruction(code, opcode, &[_]u32{ result_type_id, result_id });
269 try writeInstruction(code, opcode, &[_]Word{ result_type_id, result_id });
267270 },
268271 .Float => {
269272 // At this point we are guaranteed that the target floating point type is supported, otherwise the function
......@@ -272,15 +275,15 @@ pub const DeclGen = struct {
272275 // f16 and f32 require one word of storage. f64 requires 2, low-order first.
273276
274277 switch (ty.floatBits(target)) {
275 16 => try writeInstruction(code, .OpConstant, &[_]u32{ result_type_id, result_id, @bitCast(u16, val.toFloat(f16)) }),
276 32 => try writeInstruction(code, .OpConstant, &[_]u32{ result_type_id, result_id, @bitCast(u32, val.toFloat(f32)) }),
278 16 => try writeInstruction(code, .OpConstant, &[_]Word{ result_type_id, result_id, @bitCast(u16, val.toFloat(f16)) }),
279 32 => try writeInstruction(code, .OpConstant, &[_]Word{ result_type_id, result_id, @bitCast(u32, val.toFloat(f32)) }),
277280 64 => {
278281 const float_bits = @bitCast(u64, val.toFloat(f64));
279 try writeInstruction(code, .OpConstant, &[_]u32{
282 try writeInstruction(code, .OpConstant, &[_]Word{
280283 result_type_id,
281284 result_id,
282 @truncate(u32, float_bits),
283 @truncate(u32, float_bits >> 32),
285 @truncate(Word, float_bits),
286 @truncate(Word, float_bits >> 32),
284287 });
285288 },
286289 128 => unreachable, // Filtered out in the call to getOrGenType.
......@@ -294,7 +297,7 @@ pub const DeclGen = struct {
294297 return result_id;
295298 }
296299
297 fn getOrGenType(self: *DeclGen, ty: Type) Error!u32 {
300 fn getOrGenType(self: *DeclGen, ty: Type) Error!ResultId {
298301 // We can't use getOrPut here so we can recursively generate types.
299302 if (self.spv.types.get(ty)) |already_generated| {
300303 return already_generated;
......@@ -305,8 +308,8 @@ pub const DeclGen = struct {
305308 const result_id = self.spv.allocResultId();
306309
307310 switch (ty.zigTypeTag()) {
308 .Void => try writeInstruction(code, .OpTypeVoid, &[_]u32{result_id}),
309 .Bool => try writeInstruction(code, .OpTypeBool, &[_]u32{result_id}),
311 .Void => try writeInstruction(code, .OpTypeVoid, &[_]Word{result_id}),
312 .Bool => try writeInstruction(code, .OpTypeBool, &[_]Word{result_id}),
310313 .Int => {
311314 const int_info = ty.intInfo(target);
312315 const backing_bits = self.backingIntBits(int_info.bits) orelse {
......@@ -315,7 +318,7 @@ pub const DeclGen = struct {
315318 };
316319
317320 // TODO: If backing_bits != int_info.bits, a duplicate type might be generated here.
318 try writeInstruction(code, .OpTypeInt, &[_]u32{
321 try writeInstruction(code, .OpTypeInt, &[_]Word{
319322 result_id,
320323 backing_bits,
321324 switch (int_info.signedness) {
......@@ -340,7 +343,7 @@ pub const DeclGen = struct {
340343 return self.fail(.{ .node_offset = 0 }, "Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
341344 }
342345
343 try writeInstruction(code, .OpTypeFloat, &[_]u32{ result_id, bits });
346 try writeInstruction(code, .OpTypeFloat, &[_]Word{ result_id, bits });
344347 },
345348 .Fn => {
346349 // We only support zig-calling-convention functions, no varargs.
......@@ -360,7 +363,7 @@ pub const DeclGen = struct {
360363 const return_type_id = try self.getOrGenType(ty.fnReturnType());
361364
362365 // result id + result type id + parameter type ids.
363 try writeOpcode(code, .OpTypeFunction, 2 + @intCast(u32, ty.fnParamLen()));
366 try writeOpcode(code, .OpTypeFunction, 2 + @intCast(u16, ty.fnParamLen()));
364367 try code.appendSlice(&.{ result_id, return_type_id });
365368
366369 i = 0;
......@@ -397,7 +400,6 @@ pub const DeclGen = struct {
397400 return result_id;
398401 }
399402
400<<<<<<< HEAD
401403 pub fn gen(self: *DeclGen) !void {
402404 const decl = self.decl;
403405 const result_id = decl.fn_link.spirv.id;
......@@ -405,21 +407,10 @@ pub const DeclGen = struct {
405407 if (decl.val.castTag(.function)) |func_payload| {
406408 std.debug.assert(decl.ty.zigTypeTag() == .Fn);
407409 const prototype_id = try self.getOrGenType(decl.ty);
408 try writeInstruction(&self.spv.fn_decls, .OpFunction, &[_]u32{
409 self.types.get(decl.ty.fnReturnType()).?, // This type should be generated along with the prototype.
410=======
411 pub fn gen(self: *DeclGen) Error!void {
412 const result_id = self.decl.fn_link.spirv.id;
413 const tv = self.decl.typed_value.most_recent.typed_value;
414
415 if (tv.val.castTag(.function)) |func_payload| {
416 std.debug.assert(tv.ty.zigTypeTag() == .Fn);
417 const prototype_id = try self.getOrGenType(tv.ty);
418 try writeInstruction(&self.spv.binary.fn_decls, .OpFunction, &[_]u32{
419 self.spv.types.get(tv.ty.fnReturnType()).?, // This type should be generated along with the prototype.
420>>>>>>> 09e563b75 (SPIR-V: Put types in SPIRVModule, some general restructuring)
410 try writeInstruction(&self.spv.binary.fn_decls, .OpFunction, &[_]Word{
411 self.spv.types.get(decl.ty.fnReturnType()).?, // This type should be generated along with the prototype.
421412 result_id,
422 @bitCast(u32, spec.FunctionControl{}), // TODO: We can set inline here if the type requires it.
413 @bitCast(Word, spec.FunctionControl{}), // TODO: We can set inline here if the type requires it.
423414 prototype_id,
424415 });
425416
......@@ -428,22 +419,18 @@ pub const DeclGen = struct {
428419
429420 try self.args.ensureCapacity(params);
430421 while (i < params) : (i += 1) {
431<<<<<<< HEAD
432 const param_type_id = self.types.get(decl.ty.fnParamType(i)).?;
433=======
434 const param_type_id = self.spv.types.get(tv.ty.fnParamType(i)).?;
435>>>>>>> 09e563b75 (SPIR-V: Put types in SPIRVModule, some general restructuring)
422 const param_type_id = self.spv.types.get(decl.ty.fnParamType(i)).?;
436423 const arg_result_id = self.spv.allocResultId();
437 try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionParameter, &[_]u32{ param_type_id, arg_result_id });
424 try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionParameter, &[_]Word{ param_type_id, arg_result_id });
438425 self.args.appendAssumeCapacity(arg_result_id);
439426 }
440427
441428 // TODO: This could probably be done in a better way...
442429 const root_block_id = self.spv.allocResultId();
443 _ = try writeInstruction(&self.spv.binary.fn_decls, .OpLabel, &[_]u32{root_block_id});
430 _ = try writeInstruction(&self.spv.binary.fn_decls, .OpLabel, &[_]Word{root_block_id});
444431 try self.genBody(func_payload.data.body);
445432
446 try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionEnd, &[_]u32{});
433 try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionEnd, &[_]Word{});
447434 } else {
448435 return self.fail(.{ .node_offset = 0 }, "TODO: SPIR-V backend: generate decl type {}", .{decl.ty.zigTypeTag()});
449436 }
......@@ -457,7 +444,7 @@ pub const DeclGen = struct {
457444 }
458445 }
459446
460 fn genInst(self: *DeclGen, inst: *Inst) !?u32 {
447 fn genInst(self: *DeclGen, inst: *Inst) !?ResultId {
461448 return switch (inst.tag) {
462449 .add, .addwrap => try self.genBinOp(inst.castTag(.add).?),
463450 .sub, .subwrap => try self.genBinOp(inst.castTag(.sub).?),
......@@ -487,7 +474,7 @@ pub const DeclGen = struct {
487474 };
488475 }
489476
490 fn genBinOp(self: *DeclGen, inst: *Inst.BinOp) !u32 {
477 fn genBinOp(self: *DeclGen, inst: *Inst.BinOp) !ResultId {
491478 // TODO: Will lhs and rhs have the same type?
492479 const lhs_id = try self.resolve(inst.lhs);
493480 const rhs_id = try self.resolve(inst.rhs);
......@@ -546,7 +533,7 @@ pub const DeclGen = struct {
546533 else => unreachable,
547534 };
548535
549 try writeInstruction(&self.spv.binary.fn_decls, opcode, &[_]u32{ result_type_id, result_id, lhs_id, rhs_id });
536 try writeInstruction(&self.spv.binary.fn_decls, opcode, &[_]Word{ result_type_id, result_id, lhs_id, rhs_id });
550537
551538 // TODO: Trap on overflow? Probably going to be annoying.
552539 // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap.
......@@ -557,7 +544,7 @@ pub const DeclGen = struct {
557544 return self.fail(.{ .node_offset = 0 }, "TODO: SPIR-V backend: strange integer operation mask", .{});
558545 }
559546
560 fn genUnOp(self: *DeclGen, inst: *Inst.UnOp) !u32 {
547 fn genUnOp(self: *DeclGen, inst: *Inst.UnOp) !ResultId {
561548 const operand_id = try self.resolve(inst.operand);
562549
563550 const result_id = self.spv.allocResultId();
......@@ -571,32 +558,32 @@ pub const DeclGen = struct {
571558 else => unreachable,
572559 };
573560
574 try writeInstruction(&self.spv.binary.fn_decls, opcode, &[_]u32{ result_type_id, result_id, operand_id });
561 try writeInstruction(&self.spv.binary.fn_decls, opcode, &[_]Word{ result_type_id, result_id, operand_id });
575562
576563 return result_id;
577564 }
578565
579 fn genArg(self: *DeclGen) u32 {
566 fn genArg(self: *DeclGen) ResultId {
580567 defer self.next_arg_index += 1;
581568 return self.args.items[self.next_arg_index];
582569 }
583570
584 fn genRet(self: *DeclGen, inst: *Inst.UnOp) !?u32 {
571 fn genRet(self: *DeclGen, inst: *Inst.UnOp) !?ResultId {
585572 const operand_id = try self.resolve(inst.operand);
586573 // TODO: This instruction needs to be the last in a block. Is that guaranteed?
587 try writeInstruction(&self.spv.binary.fn_decls, .OpReturnValue, &[_]u32{operand_id});
574 try writeInstruction(&self.spv.binary.fn_decls, .OpReturnValue, &[_]Word{operand_id});
588575 return null;
589576 }
590577
591 fn genRetVoid(self: *DeclGen) !?u32 {
578 fn genRetVoid(self: *DeclGen) !?ResultId {
592579 // TODO: This instruction needs to be the last in a block. Is that guaranteed?
593 try writeInstruction(&self.spv.binary.fn_decls, .OpReturn, &[_]u32{});
580 try writeInstruction(&self.spv.binary.fn_decls, .OpReturn, &[_]Word{});
594581 return null;
595582 }
596583
597 fn genUnreach(self: *DeclGen) !?u32 {
584 fn genUnreach(self: *DeclGen) !?ResultId {
598585 // TODO: This instruction needs to be the last in a block. Is that guaranteed?
599 try writeInstruction(&self.spv.binary.fn_decls, .OpUnreachable, &[_]u32{});
586 try writeInstruction(&self.spv.binary.fn_decls, .OpUnreachable, &[_]Word{});
600587 return null;
601588 }
602589};
src/link/SpirV.zig+14-11
......@@ -31,15 +31,18 @@ const Module = @import("../Module.zig");
3131const Compilation = @import("../Compilation.zig");
3232const link = @import("../link.zig");
3333const codegen = @import("../codegen/spirv.zig");
34const Word = codegen.Word;
35const ResultId = codegen.ResultId;
3436const trace = @import("../tracy.zig").trace;
3537const build_options = @import("build_options");
3638const spec = @import("../codegen/spirv/spec.zig");
3739
3840// TODO: Should this struct be used at all rather than just a hashmap of aux data for every decl?
3941pub const FnData = struct {
40// We're going to fill these in flushModule, and we're going to fill them unconditionally,
41// so just set it to undefined.
42id: u32 = undefined };
42 // We're going to fill these in flushModule, and we're going to fill them unconditionally,
43 // so just set it to undefined.
44 id: ResultId = undefined
45};
4346
4447base: link.File,
4548
......@@ -155,7 +158,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
155158 var decl_gen = codegen.DeclGen{
156159 .module = module,
157160 .spv = &spv,
158 .args = std.ArrayList(u32).init(self.base.allocator),
161 .args = std.ArrayList(codegen.Word).init(self.base.allocator),
159162 .next_arg_index = undefined,
160163 .inst_results = codegen.InstMap.init(self.base.allocator),
161164 .decl = undefined,
......@@ -185,10 +188,10 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
185188 }
186189 }
187190
188 var binary = std.ArrayList(u32).init(self.base.allocator);
191 var binary = std.ArrayList(Word).init(self.base.allocator);
189192 defer binary.deinit();
190193
191 try binary.appendSlice(&[_]u32{
194 try binary.appendSlice(&[_]Word{
192195 spec.magic_number,
193196 (spec.version.major << 16) | (spec.version.minor << 8),
194197 0, // TODO: Register Zig compiler magic number.
......@@ -220,7 +223,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
220223 try file.pwritevAll(&all_buffers, 0);
221224}
222225
223fn writeCapabilities(binary: *std.ArrayList(u32), target: std.Target) !void {
226fn writeCapabilities(binary: *std.ArrayList(Word), target: std.Target) !void {
224227 // TODO: Integrate with a hypothetical feature system
225228 const cap: spec.Capability = switch (target.os.tag) {
226229 .opencl => .Kernel,
......@@ -229,10 +232,10 @@ fn writeCapabilities(binary: *std.ArrayList(u32), target: std.Target) !void {
229232 else => unreachable, // TODO
230233 };
231234
232 try codegen.writeInstruction(binary, .OpCapability, &[_]u32{@enumToInt(cap)});
235 try codegen.writeInstruction(binary, .OpCapability, &[_]Word{@enumToInt(cap)});
233236}
234237
235fn writeMemoryModel(binary: *std.ArrayList(u32), target: std.Target) !void {
238fn writeMemoryModel(binary: *std.ArrayList(Word), target: std.Target) !void {
236239 const addressing_model = switch (target.os.tag) {
237240 .opencl => switch (target.cpu.arch) {
238241 .spirv32 => spec.AddressingModel.Physical32,
......@@ -250,12 +253,12 @@ fn writeMemoryModel(binary: *std.ArrayList(u32), target: std.Target) !void {
250253 else => unreachable,
251254 };
252255
253 try codegen.writeInstruction(binary, .OpMemoryModel, &[_]u32{
256 try codegen.writeInstruction(binary, .OpMemoryModel, &[_]Word{
254257 @enumToInt(addressing_model), @enumToInt(memory_model),
255258 });
256259}
257260
258fn wordsToIovConst(words: []const u32) std.os.iovec_const {
261fn wordsToIovConst(words: []const Word) std.os.iovec_const {
259262 const bytes = std.mem.sliceAsBytes(words);
260263 return .{
261264 .iov_base = bytes.ptr,