authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-15 09:43:57+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-16 14:13:23+02:00
logcbf5280f54509e7aa58d8fd14258274a12efeee1
tree8acf462cd3b0a94b13057b06f7748d7906bd107a
parentda0cc732ea899d2284200faf54c3c12e8c798b7f

SPIR-V: Some instructions + constant generation setup


2 files changed, 135 insertions(+), 21 deletions(-)

src/codegen/spirv.zig+130-20
...@@ -1,16 +1,19 @@...@@ -1,16 +1,19 @@
1const std = @import("std");1const std = @import("std");
2const Allocator = std.mem.Allocator;2const Allocator = std.mem.Allocator;
3const log = std.log.scoped(.codegen);
4
5const Target = std.Target;3const Target = std.Target;
4const log = std.log.scoped(.codegen);
65
7const spec = @import("spirv/spec.zig");6const spec = @import("spirv/spec.zig");
8const Module = @import("../Module.zig");7const Module = @import("../Module.zig");
9const Decl = Module.Decl;8const Decl = Module.Decl;
10const Type = @import("../type.zig").Type;9const Type = @import("../type.zig").Type;
10const Value = @import("../value.zig").Value;
11const LazySrcLoc = Module.LazySrcLoc;11const LazySrcLoc = Module.LazySrcLoc;
12const ir = @import("../ir.zig");
13const Inst = ir.Inst;
1214
13pub const TypeMap = std.HashMap(Type, u32, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);15pub const TypeMap = std.HashMap(Type, u32, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
16pub const ValueMap = std.AutoHashMap(*Inst, u32);
1417
15pub fn writeOpcode(code: *std.ArrayList(u32), opcode: spec.Opcode, arg_count: u32) !void {18pub fn writeOpcode(code: *std.ArrayList(u32), opcode: spec.Opcode, arg_count: u32) !void {
16 const word_count = arg_count + 1;19 const word_count = arg_count + 1;
...@@ -26,19 +29,19 @@ pub fn writeInstruction(code: *std.ArrayList(u32), opcode: spec.Opcode, args: []...@@ -26,19 +29,19 @@ pub fn writeInstruction(code: *std.ArrayList(u32), opcode: spec.Opcode, args: []
26/// such as code for the different logical sections, and the next result-id.29/// such as code for the different logical sections, and the next result-id.
27pub const SPIRVModule = struct {30pub const SPIRVModule = struct {
28 next_result_id: u32,31 next_result_id: u32,
29 types_and_globals: std.ArrayList(u32),32 types_globals_constants: std.ArrayList(u32),
30 fn_decls: std.ArrayList(u32),33 fn_decls: std.ArrayList(u32),
3134
32 pub fn init(allocator: *Allocator) SPIRVModule {35 pub fn init(allocator: *Allocator) SPIRVModule {
33 return .{36 return .{
34 .next_result_id = 1, // 0 is an invalid SPIR-V result ID.37 .next_result_id = 1, // 0 is an invalid SPIR-V result ID.
35 .types_and_globals = std.ArrayList(u32).init(allocator),38 .types_globals_constants = std.ArrayList(u32).init(allocator),
36 .fn_decls = std.ArrayList(u32).init(allocator),39 .fn_decls = std.ArrayList(u32).init(allocator),
37 };40 };
38 }41 }
3942
40 pub fn deinit(self: *SPIRVModule) void {43 pub fn deinit(self: *SPIRVModule) void {
41 self.types_and_globals.deinit();44 self.types_globals_constants.deinit();
42 self.fn_decls.deinit();45 self.fn_decls.deinit();
43 }46 }
4447
...@@ -58,7 +61,10 @@ pub const DeclGen = struct {...@@ -58,7 +61,10 @@ pub const DeclGen = struct {
58 spv: *SPIRVModule,61 spv: *SPIRVModule,
5962
60 args: std.ArrayList(u32),63 args: std.ArrayList(u32),
64 next_arg_index: u32,
65
61 types: TypeMap,66 types: TypeMap,
67 values: ValueMap,
6268
63 decl: *Decl,69 decl: *Decl,
64 error_msg: ?*Module.ErrorMsg,70 error_msg: ?*Module.ErrorMsg,
...@@ -75,6 +81,14 @@ pub const DeclGen = struct {...@@ -75,6 +81,14 @@ pub const DeclGen = struct {
75 return error.AnalysisFail;81 return error.AnalysisFail;
76 }82 }
7783
84 fn resolve(self: *DeclGen, inst: *Inst) !u32 {
85 if (inst.value()) |val| {
86 return self.genConstant(inst.ty, val);
87 }
88
89 return self.values.get(inst).?; // Instruction does not dominate all uses!
90 }
91
78 /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need92 /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need
79 /// to emulate them in other instructions/types. This function returns, given an integer bit width (signed or unsigned, sign93 /// to emulate them in other instructions/types. This function returns, given an integer bit width (signed or unsigned, sign
80 /// included), the width of the underlying type which represents it, given the enabled features for the current target.94 /// included), the width of the underlying type which represents it, given the enabled features for the current target.
...@@ -82,13 +96,16 @@ pub const DeclGen = struct {...@@ -82,13 +96,16 @@ pub const DeclGen = struct {
82 /// that size. In this case, multiple elements of the largest type should be used.96 /// that size. In this case, multiple elements of the largest type should be used.
83 /// The backing type will be chosen as the smallest supported integer larger or equal to it in number of bits.97 /// The backing type will be chosen as the smallest supported integer larger or equal to it in number of bits.
84 /// The result is valid to be used with OpTypeInt.98 /// The result is valid to be used with OpTypeInt.
99 /// asserts `ty` is an integer.
85 /// TODO: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits).100 /// TODO: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits).
86 /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).101 /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).
87 fn backingIntBits(self: *DeclGen, bits: u32) ?u32 {102 /// TODO: Should the result of this function be cached?
88 // TODO: Figure out what to do with u0/i0.103 fn backingIntBits(self: *DeclGen, ty: Type) ?u32 {
89 std.debug.assert(bits != 0);
90
91 const target = self.module.getTarget();104 const target = self.module.getTarget();
105 const int_info = ty.intInfo(target);
106
107 // TODO: Figure out what to do with u0/i0.
108 std.debug.assert(int_info.bits != 0);
92109
93 // 8, 16 and 64-bit integers require the Int8, Int16 and Inr64 capabilities respectively.110 // 8, 16 and 64-bit integers require the Int8, Int16 and Inr64 capabilities respectively.
94 const ints = [_]struct{ bits: u32, feature: ?Target.spirv.Feature } {111 const ints = [_]struct{ bits: u32, feature: ?Target.spirv.Feature } {
...@@ -104,7 +121,7 @@ pub const DeclGen = struct {...@@ -104,7 +121,7 @@ pub const DeclGen = struct {
104 else121 else
105 true;122 true;
106123
107 if (bits <= int.bits and has_feature) {124 if (int_info.bits <= int.bits and has_feature) {
108 return int.bits;125 return int.bits;
109 }126 }
110 }127 }
...@@ -112,6 +129,43 @@ pub const DeclGen = struct {...@@ -112,6 +129,43 @@ pub const DeclGen = struct {
112 return null;129 return null;
113 }130 }
114131
132 /// Return the amount of bits in the largest supported integer type. This is either 32 (always supported), or 64 (if
133 /// the Int64 capability is enabled).
134 /// Note: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits).
135 /// In theory that could also be used, but since the spec says that it only guarantees support up to 32-bit ints there
136 /// is no way of knowing whether those are actually supported.
137 /// TODO: Maybe this should be cached?
138 fn largestSupportedIntBits(self: *DeclGen) u32 {
139 const target = self.module.getTarget();
140 return if (Target.spirv.featureSetHas(target.cpu.features, .Int64))
141 64
142 else
143 32;
144 }
145
146 /// Generate a constant representing `val`.
147 /// TODO: Deduplication?
148 fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!u32 {
149 const code = &self.spv.types_globals_constants;
150 const result_id = self.spv.allocResultId();
151 const result_type_id = try self.getOrGenType(ty);
152
153 if (val.isUndef()) {
154 try writeInstruction(code, .OpUndef, &[_]u32{ result_type_id, result_id });
155 return result_id;
156 }
157
158 switch (ty.zigTypeTag()) {
159 .Bool => {
160 const opcode: spec.Opcode = if (val.toBool()) .OpConstantTrue else .OpConstantFalse;
161 try writeInstruction(code, opcode, &[_]u32{ result_type_id, result_id });
162 },
163 else => return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: constant generation of type {s}\n", .{ ty.zigTypeTag() }),
164 }
165
166 return result_id;
167 }
168
115 fn getOrGenType(self: *DeclGen, ty: Type) Error!u32 {169 fn getOrGenType(self: *DeclGen, ty: Type) Error!u32 {
116 // We can't use getOrPut here so we can recursively generate types.170 // We can't use getOrPut here so we can recursively generate types.
117 if (self.types.get(ty)) |already_generated| {171 if (self.types.get(ty)) |already_generated| {
...@@ -119,24 +173,21 @@ pub const DeclGen = struct {...@@ -119,24 +173,21 @@ pub const DeclGen = struct {
119 }173 }
120174
121 const target = self.module.getTarget();175 const target = self.module.getTarget();
122 const code = &self.spv.types_and_globals;176 const code = &self.spv.types_globals_constants;
123 const result_id = self.spv.allocResultId();177 const result_id = self.spv.allocResultId();
124178
125 switch (ty.zigTypeTag()) {179 switch (ty.zigTypeTag()) {
126 .Void => try writeInstruction(code, .OpTypeVoid, &[_]u32{ result_id }),180 .Void => try writeInstruction(code, .OpTypeVoid, &[_]u32{ result_id }),
127 .Bool => try writeInstruction(code, .OpTypeBool, &[_]u32{ result_id }),181 .Bool => try writeInstruction(code, .OpTypeBool, &[_]u32{ result_id }),
128 .Int => {182 .Int => {
129 const int_info = ty.intInfo(self.module.getTarget());183 const backing_bits = self.backingIntBits(ty) orelse
130 const backing_bits = self.backingIntBits(int_info.bits) orelse
131 return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement fallback for {}", .{ ty });184 return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement fallback for {}", .{ ty });
132185
186 // TODO: If backing_bits != int_info.bits, a duplicate type might be generated here.
133 try writeInstruction(code, .OpTypeInt, &[_]u32{187 try writeInstruction(code, .OpTypeInt, &[_]u32{
134 result_id,188 result_id,
135 backing_bits,189 backing_bits,
136 switch (int_info.signedness) {190 @boolToInt(ty.isSignedInt()),
137 .unsigned => 0,
138 .signed => 1,
139 },
140 });191 });
141 },192 },
142 .Float => {193 .Float => {
...@@ -183,6 +234,15 @@ pub const DeclGen = struct {...@@ -183,6 +234,15 @@ pub const DeclGen = struct {
183 try code.append(param_type_id);234 try code.append(param_type_id);
184 }235 }
185 },236 },
237 .Vector => {
238 // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
239 // which work on them), so simply use those.
240 // Note: SPIR-V vectors only support bools, ints and floats, so pointer vectors need to be supported another way.
241 // "big integers" (larger than the largest supported native type) can probably be represented by an array of vectors.
242
243 // TODO: Vectors are not yet supported by the self-hosted compiler itself it seems.
244 return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement type Vector", .{});
245 },
186 .Null,246 .Null,
187 .Undefined,247 .Undefined,
188 .EnumLiteral,248 .EnumLiteral,
...@@ -193,10 +253,10 @@ pub const DeclGen = struct {...@@ -193,10 +253,10 @@ pub const DeclGen = struct {
193253
194 .BoundFn => unreachable, // this type will be deleted from the language.254 .BoundFn => unreachable, // this type will be deleted from the language.
195255
196 else => |tag| return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement type {}", .{ tag }),256 else => |tag| return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement type {}s", .{ tag }),
197 }257 }
198258
199 try self.types.put(ty, result_id);259 try self.types.putNoClobber(ty, result_id);
200 return result_id;260 return result_id;
201 }261 }
202262
...@@ -225,11 +285,61 @@ pub const DeclGen = struct {...@@ -225,11 +285,61 @@ pub const DeclGen = struct {
225 self.args.appendAssumeCapacity(arg_result_id);285 self.args.appendAssumeCapacity(arg_result_id);
226 }286 }
227287
228 // TODO: Body288 // TODO: This could probably be done in a better way...
289 const root_block_id = self.spv.allocResultId();
290 _ = try writeInstruction(&self.spv.fn_decls, .OpLabel, &[_]u32{root_block_id});
291 try self.genBody(func_payload.data.body);
229292
230 try writeInstruction(&self.spv.fn_decls, .OpFunctionEnd, &[_]u32{});293 try writeInstruction(&self.spv.fn_decls, .OpFunctionEnd, &[_]u32{});
231 } else {294 } else {
232 return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: generate decl type {}", .{ tv.ty.zigTypeTag() });295 return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: generate decl type {}", .{ tv.ty.zigTypeTag() });
233 }296 }
234 }297 }
298
299 fn genBody(self: *DeclGen, body: ir.Body) !void {
300 for (body.instructions) |inst| {
301 const maybe_result_id = try self.genInst(inst);
302 if (maybe_result_id) |result_id|
303 try self.values.putNoClobber(inst, result_id);
304 }
305 }
306
307 fn genInst(self: *DeclGen, inst: *Inst) !?u32 {
308 return switch (inst.tag) {
309 .arg => self.genArg(),
310 // TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them
311 // throughout the IR.
312 .breakpoint => null,
313 // TODO: What does this entail?
314 .dbg_stmt => null,
315 .ret => self.genRet(inst.castTag(.ret).?),
316 .retvoid => self.genRetVoid(),
317 .unreach => self.genUnreach(),
318 else => self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement inst {}", .{inst.tag}),
319 };
320 }
321
322 fn genArg(self: *DeclGen) u32 {
323 defer self.next_arg_index += 1;
324 return self.args.items[self.next_arg_index];
325 }
326
327 fn genRet(self: *DeclGen, inst: *Inst.UnOp) !?u32 {
328 const operand_id = try self.resolve(inst.operand);
329 // TODO: This instruction needs to be the last in a block. Is that guaranteed?
330 try writeInstruction(&self.spv.fn_decls, .OpReturnValue, &[_]u32{ operand_id });
331 return null;
332 }
333
334 fn genRetVoid(self: *DeclGen) !?u32 {
335 // TODO: This instruction needs to be the last in a block. Is that guaranteed?
336 try writeInstruction(&self.spv.fn_decls, .OpReturn, &[_]u32{});
337 return null;
338 }
339
340 fn genUnreach(self: *DeclGen) !?u32 {
341 // TODO: This instruction needs to be the last in a block. Is that guaranteed?
342 try writeInstruction(&self.spv.fn_decls, .OpUnreachable, &[_]u32{});
343 return null;
344 }
235};345};
src/link/SpirV.zig+5-1
...@@ -146,11 +146,14 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {...@@ -146,11 +146,14 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
146 .module = module,146 .module = module,
147 .spv = &spv,147 .spv = &spv,
148 .args = std.ArrayList(u32).init(self.base.allocator),148 .args = std.ArrayList(u32).init(self.base.allocator),
149 .next_arg_index = undefined,
149 .types = codegen.TypeMap.init(self.base.allocator),150 .types = codegen.TypeMap.init(self.base.allocator),
151 .values = codegen.ValueMap.init(self.base.allocator),
150 .decl = undefined,152 .decl = undefined,
151 .error_msg = undefined,153 .error_msg = undefined,
152 };154 };
153155
156 defer decl_gen.values.deinit();
154 defer decl_gen.types.deinit();157 defer decl_gen.types.deinit();
155 defer decl_gen.args.deinit();158 defer decl_gen.args.deinit();
156159
...@@ -160,6 +163,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {...@@ -160,6 +163,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
160 continue;163 continue;
161164
162 decl_gen.args.items.len = 0;165 decl_gen.args.items.len = 0;
166 decl_gen.next_arg_index = 0;
163 decl_gen.decl = decl;167 decl_gen.decl = decl;
164 decl_gen.error_msg = null;168 decl_gen.error_msg = null;
165169
...@@ -191,7 +195,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {...@@ -191,7 +195,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
191 // follows the SPIR-V logical module format!195 // follows the SPIR-V logical module format!
192 var all_buffers = [_]std.os.iovec_const{196 var all_buffers = [_]std.os.iovec_const{
193 wordsToIovConst(binary.items),197 wordsToIovConst(binary.items),
194 wordsToIovConst(spv.types_and_globals.items),198 wordsToIovConst(spv.types_globals_constants.items),
195 wordsToIovConst(spv.fn_decls.items),199 wordsToIovConst(spv.fn_decls.items),
196 };200 };
197201