authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-17 21:25:02-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-05-17 21:25:02-04:00
log0dd0c9620d66afcfabaf3dcb21b636530fd0ccba
treebcf759865377dd0c83ed4bc02160b51bc35fa9ab
parent65cee0b3fd5f9b3f83b79cc8fd1b64d13f4dd0c4
parent880473dc3f08e2f8c0cef85777d50e25e4bcb062
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8796 from Snektron/spirv

SPIR-V: Codegen basis

2 files changed, 551 insertions(+), 47 deletions(-)

src/codegen/spirv.zig+515-40
......@@ -1,44 +1,50 @@
11const std = @import("std");
22const Allocator = std.mem.Allocator;
3const Target = std.Target;
34const log = std.log.scoped(.codegen);
45
56const spec = @import("spirv/spec.zig");
7const Opcode = spec.Opcode;
8
69const Module = @import("../Module.zig");
710const Decl = Module.Decl;
811const Type = @import("../type.zig").Type;
12const Value = @import("../value.zig").Value;
13const LazySrcLoc = Module.LazySrcLoc;
14const ir = @import("../ir.zig");
15const Inst = ir.Inst;
916
1017pub const TypeMap = std.HashMap(Type, u32, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
18pub const ValueMap = std.AutoHashMap(*Inst, u32);
19
20pub fn writeOpcode(code: *std.ArrayList(u32), opcode: Opcode, arg_count: u32) !void {
21 const word_count = arg_count + 1;
22 try code.append((word_count << 16) | @enumToInt(opcode));
23}
1124
12pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void {
13 const word_count = @intCast(u32, args.len + 1);
14 try code.append((word_count << 16) | @enumToInt(instr));
25pub fn writeInstruction(code: *std.ArrayList(u32), opcode: Opcode, args: []const u32) !void {
26 try writeOpcode(code, opcode, @intCast(u32, args.len));
1527 try code.appendSlice(args);
1628}
1729
30/// This structure represents a SPIR-V binary module being compiled, and keeps track of relevant information
31/// such as code for the different logical sections, and the next result-id.
1832pub const SPIRVModule = struct {
19 next_result_id: u32 = 0,
20
21 target: std.Target,
22
23 types: TypeMap,
24
25 types_and_globals: std.ArrayList(u32),
33 next_result_id: u32,
34 types_globals_constants: std.ArrayList(u32),
2635 fn_decls: std.ArrayList(u32),
2736
28 pub fn init(target: std.Target, allocator: *Allocator) SPIRVModule {
37 pub fn init(allocator: *Allocator) SPIRVModule {
2938 return .{
30 .target = target,
31 .types = TypeMap.init(allocator),
32 .types_and_globals = std.ArrayList(u32).init(allocator),
39 .next_result_id = 1, // 0 is an invalid SPIR-V result ID.
40 .types_globals_constants = std.ArrayList(u32).init(allocator),
3341 .fn_decls = std.ArrayList(u32).init(allocator),
3442 };
3543 }
3644
3745 pub fn deinit(self: *SPIRVModule) void {
46 self.types_globals_constants.deinit();
3847 self.fn_decls.deinit();
39 self.types_and_globals.deinit();
40 self.types.deinit();
41 self.* = undefined;
4248 }
4349
4450 pub fn allocResultId(self: *SPIRVModule) u32 {
......@@ -49,31 +55,326 @@ pub const SPIRVModule = struct {
4955 pub fn resultIdBound(self: *SPIRVModule) u32 {
5056 return self.next_result_id;
5157 }
58};
59
60/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
61pub const DeclGen = struct {
62 module: *Module,
63 spv: *SPIRVModule,
64
65 args: std.ArrayList(u32),
66 next_arg_index: u32,
67
68 types: TypeMap,
69 values: ValueMap,
70
71 decl: *Decl,
72 error_msg: ?*Module.ErrorMsg,
73
74 const Error = error{
75 AnalysisFail,
76 OutOfMemory
77 };
78
79 /// This structure is used to return information about a type typically used for arithmetic operations.
80 /// These types may either be integers, floats, or a vector of these. Most scalar operations also work on vectors,
81 /// so we can easily represent those as arithmetic types.
82 /// If the type is a scalar, 'inner type' refers to the scalar type. Otherwise, if its a vector, it refers
83 /// to the vector's element type.
84 const ArithmeticTypeInfo = struct {
85 /// A classification of the inner type.
86 const Class = enum {
87 /// A boolean.
88 bool,
5289
53 pub fn getOrGenType(self: *SPIRVModule, t: Type) !u32 {
90 /// A regular, **native**, integer.
91 /// This is only returned when the backend supports this int as a native type (when
92 /// the relevant capability is enabled).
93 integer,
94
95 /// A regular float. These are all required to be natively supported. Floating points for
96 /// which the relevant capability is not enabled are not emulated.
97 float,
98
99 /// An integer of a 'strange' size (which' bit size is not the same as its backing type. **Note**: this
100 /// may **also** include power-of-2 integers for which the relevant capability is not enabled), but still
101 /// within the limits of the largest natively supported integer type.
102 strange_integer,
103
104 /// An integer with more bits than the largest natively supported integer type.
105 composite_integer,
106 };
107
108 /// The number of bits in the inner type.
109 /// Note: this is the actual number of bits of the type, not the size of the backing integer.
110 bits: u16,
111
112 /// Whether the type is a vector.
113 is_vector: bool,
114
115 /// Whether the inner type is signed. Only relevant for integers.
116 signedness: std.builtin.Signedness,
117
118 /// A classification of the inner type. These scenarios
119 /// will all have to be handled slightly different.
120 class: Class,
121 };
122
123 fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) Error {
124 @setCold(true);
125 const src_loc = src.toSrcLocWithDecl(self.decl);
126 self.error_msg = try Module.ErrorMsg.create(self.module.gpa, src_loc, format, args);
127 return error.AnalysisFail;
128 }
129
130 fn resolve(self: *DeclGen, inst: *Inst) !u32 {
131 if (inst.value()) |val| {
132 return self.genConstant(inst.ty, val);
133 }
134
135 return self.values.get(inst).?; // Instruction does not dominate all uses!
136 }
137
138 /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need
139 /// to emulate them in other instructions/types. This function returns, given an integer bit width (signed or unsigned, sign
140 /// included), the width of the underlying type which represents it, given the enabled features for the current target.
141 /// If the result is `null`, the largest type the target platform supports natively is not able to perform computations using
142 /// that size. In this case, multiple elements of the largest type should be used.
143 /// The backing type will be chosen as the smallest supported integer larger or equal to it in number of bits.
144 /// The result is valid to be used with OpTypeInt.
145 /// TODO: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits).
146 /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).
147 /// TODO: Should the result of this function be cached?
148 fn backingIntBits(self: *DeclGen, bits: u16) ?u16 {
149 const target = self.module.getTarget();
150
151 // TODO: Figure out what to do with u0/i0.
152 std.debug.assert(bits != 0);
153
154 // 8, 16 and 64-bit integers require the Int8, Int16 and Inr64 capabilities respectively.
155 // 32-bit integers are always supported (see spec, 2.16.1, Data rules).
156 const ints = [_]struct{ bits: u16, feature: ?Target.spirv.Feature } {
157 .{ .bits = 8, .feature = .Int8 },
158 .{ .bits = 16, .feature = .Int16 },
159 .{ .bits = 32, .feature = null },
160 .{ .bits = 64, .feature = .Int64 },
161 };
162
163 for (ints) |int| {
164 const has_feature = if (int.feature) |feature|
165 Target.spirv.featureSetHas(target.cpu.features, feature)
166 else
167 true;
168
169 if (bits <= int.bits and has_feature) {
170 return int.bits;
171 }
172 }
173
174 return null;
175 }
176
177 /// Return the amount of bits in the largest supported integer type. This is either 32 (always supported), or 64 (if
178 /// the Int64 capability is enabled).
179 /// Note: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits).
180 /// In theory that could also be used, but since the spec says that it only guarantees support up to 32-bit ints there
181 /// is no way of knowing whether those are actually supported.
182 /// TODO: Maybe this should be cached?
183 fn largestSupportedIntBits(self: *DeclGen) u16 {
184 const target = self.module.getTarget();
185 return if (Target.spirv.featureSetHas(target.cpu.features, .Int64))
186 64
187 else
188 32;
189 }
190
191 /// Checks whether the type is "composite int", an integer consisting of multiple native integers. These are represented by
192 /// arrays of largestSupportedIntBits().
193 /// Asserts `ty` is an integer.
194 fn isCompositeInt(self: *DeclGen, ty: Type) bool {
195 return self.backingIntBits(ty) == null;
196 }
197
198 fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo {
199 const target = self.module.getTarget();
200
201 return switch (ty.zigTypeTag()) {
202 .Bool => ArithmeticTypeInfo{
203 .bits = 1, // Doesn't matter for this class.
204 .is_vector = false,
205 .signedness = .unsigned, // Technically, but doesn't matter for this class.
206 .class = .bool,
207 },
208 .Float => ArithmeticTypeInfo{
209 .bits = ty.floatBits(target),
210 .is_vector = false,
211 .signedness = .signed, // Technically, but doesn't matter for this class.
212 .class = .float,
213 },
214 .Int => blk: {
215 const int_info = ty.intInfo(target);
216 // TODO: Maybe it's useful to also return this value.
217 const maybe_backing_bits = self.backingIntBits(int_info.bits);
218 break :blk ArithmeticTypeInfo{
219 .bits = int_info.bits,
220 .is_vector = false,
221 .signedness = int_info.signedness,
222 .class = if (maybe_backing_bits) |backing_bits|
223 if (backing_bits == int_info.bits)
224 ArithmeticTypeInfo.Class.integer
225 else
226 ArithmeticTypeInfo.Class.strange_integer
227 else
228 .composite_integer
229 };
230 },
231 // As of yet, there is no vector support in the self-hosted compiler.
232 .Vector => self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}),
233 // TODO: For which types is this the case?
234 else => self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement arithmeticTypeInfo for {}", .{ty}),
235 };
236 }
237
238 /// Generate a constant representing `val`.
239 /// TODO: Deduplication?
240 fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!u32 {
241 const code = &self.spv.types_globals_constants;
242 const result_id = self.spv.allocResultId();
243 const result_type_id = try self.getOrGenType(ty);
244
245 if (val.isUndef()) {
246 try writeInstruction(code, .OpUndef, &[_]u32{ result_type_id, result_id });
247 return result_id;
248 }
249
250 switch (ty.zigTypeTag()) {
251 .Bool => {
252 const opcode: Opcode = if (val.toBool()) .OpConstantTrue else .OpConstantFalse;
253 try writeInstruction(code, opcode, &[_]u32{ result_type_id, result_id });
254 },
255 .Float => {
256 // At this point we are guaranteed that the target floating point type is supported, otherwise the function
257 // would have exited at getOrGenType(ty).
258
259 // f16 and f32 require one word of storage. f64 requires 2, low-order first.
260
261 switch (val.tag()) {
262 .float_16 => try writeInstruction(code, .OpConstant, &[_]u32{
263 result_type_id,
264 result_id,
265 @bitCast(u16, val.castTag(.float_16).?.data)
266 }),
267 .float_32 => try writeInstruction(code, .OpConstant, &[_]u32{
268 result_type_id,
269 result_id,
270 @bitCast(u32, val.castTag(.float_32).?.data)
271 }),
272 .float_64 => {
273 const float_bits = @bitCast(u64, val.castTag(.float_64).?.data);
274 try writeInstruction(code, .OpConstant, &[_]u32{
275 result_type_id,
276 result_id,
277 @truncate(u32, float_bits),
278 @truncate(u32, float_bits >> 32),
279 });
280 },
281 .float_128 => unreachable, // Filtered out in the call to getOrGenType.
282 // TODO: What tags do we need to handle here anyway?
283 else => return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: float constant generation of value {s}\n", .{ val.tag() }),
284 }
285 },
286 else => return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: constant generation of type {s}\n", .{ ty.zigTypeTag() }),
287 }
288
289 return result_id;
290 }
291
292 fn getOrGenType(self: *DeclGen, ty: Type) Error!u32 {
54293 // We can't use getOrPut here so we can recursively generate types.
55 if (self.types.get(t)) |already_generated| {
294 if (self.types.get(ty)) |already_generated| {
56295 return already_generated;
57296 }
58297
59 const result = self.allocResultId();
298 const target = self.module.getTarget();
299 const code = &self.spv.types_globals_constants;
300 const result_id = self.spv.allocResultId();
60301
61 switch (t.zigTypeTag()) {
62 .Void => try writeInstruction(&self.types_and_globals, .OpTypeVoid, &[_]u32{ result }),
63 .Bool => try writeInstruction(&self.types_and_globals, .OpTypeBool, &[_]u32{ result }),
302 switch (ty.zigTypeTag()) {
303 .Void => try writeInstruction(code, .OpTypeVoid, &[_]u32{ result_id }),
304 .Bool => try writeInstruction(code, .OpTypeBool, &[_]u32{ result_id }),
64305 .Int => {
65 const int_info = t.intInfo(self.target);
66 try writeInstruction(&self.types_and_globals, .OpTypeInt, &[_]u32{
67 result,
68 int_info.bits,
306 const int_info = ty.intInfo(target);
307 const backing_bits = self.backingIntBits(int_info.bits) orelse {
308 // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.
309 return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement composite ints {}", .{ ty });
310 };
311
312 // TODO: If backing_bits != int_info.bits, a duplicate type might be generated here.
313 try writeInstruction(code, .OpTypeInt, &[_]u32{
314 result_id,
315 backing_bits,
69316 switch (int_info.signedness) {
70317 .unsigned => 0,
71318 .signed => 1,
72319 },
73320 });
74321 },
75 // TODO: Verify that floatBits() will be correct.
76 .Float => try writeInstruction(&self.types_and_globals, .OpTypeFloat, &[_]u32{ result, t.floatBits(self.target) }),
322 .Float => {
323 // We can (and want) not really emulate floating points with other floating point types like with the integer types,
324 // so if the float is not supported, just return an error.
325 const bits = ty.floatBits(target);
326 const supported = switch (bits) {
327 16 => Target.spirv.featureSetHas(target.cpu.features, .Float16),
328 // 32-bit floats are always supported (see spec, 2.16.1, Data rules).
329 32 => true,
330 64 => Target.spirv.featureSetHas(target.cpu.features, .Float64),
331 else => false,
332 };
333
334 if (!supported) {
335 return self.fail(.{.node_offset = 0}, "Floating point width of {} bits is not supported for the current SPIR-V feature set", .{ bits });
336 }
337
338 try writeInstruction(code, .OpTypeFloat, &[_]u32{ result_id, bits });
339 },
340 .Fn => {
341 // We only support zig-calling-convention functions, no varargs.
342 if (ty.fnCallingConvention() != .Unspecified)
343 return self.fail(.{.node_offset = 0}, "Unsupported calling convention for SPIR-V", .{});
344 if (ty.fnIsVarArgs())
345 return self.fail(.{.node_offset = 0}, "VarArgs unsupported for SPIR-V", .{});
346
347 // In order to avoid a temporary here, first generate all the required types and then simply look them up
348 // when generating the function type.
349 const params = ty.fnParamLen();
350 var i: usize = 0;
351 while (i < params) : (i += 1) {
352 _ = try self.getOrGenType(ty.fnParamType(i));
353 }
354
355 const return_type_id = try self.getOrGenType(ty.fnReturnType());
356
357 // result id + result type id + parameter type ids.
358 try writeOpcode(code, .OpTypeFunction, 2 + @intCast(u32, ty.fnParamLen()) );
359 try code.appendSlice(&.{ result_id, return_type_id });
360
361 i = 0;
362 while (i < params) : (i += 1) {
363 const param_type_id = self.types.get(ty.fnParamType(i)).?;
364 try code.append(param_type_id);
365 }
366 },
367 .Vector => {
368 // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
369 // which work on them), so simply use those.
370 // Note: SPIR-V vectors only support bools, ints and floats, so pointer vectors need to be supported another way.
371 // "composite integers" (larger than the largest supported native type) can probably be represented by an array of vectors.
372 // TODO: The SPIR-V spec mentions that vector sizes may be quite restricted! look into which we can use, and whether OpTypeVector
373 // is adequate at all for this.
374
375 // TODO: Vectors are not yet supported by the self-hosted compiler itself it seems.
376 return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement type Vector", .{});
377 },
77378 .Null,
78379 .Undefined,
79380 .EnumLiteral,
......@@ -84,23 +385,197 @@ pub const SPIRVModule = struct {
84385
85386 .BoundFn => unreachable, // this type will be deleted from the language.
86387
87 else => return error.TODO,
388 else => |tag| return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement type {}s", .{ tag }),
88389 }
89390
90 try self.types.put(t, result);
91 return result;
391 try self.types.putNoClobber(ty, result_id);
392 return result_id;
92393 }
93394
94 pub fn gen(self: *SPIRVModule, decl: *Decl) !void {
95 const typed_value = decl.typed_value.most_recent.typed_value;
395 pub fn gen(self: *DeclGen) !void {
396 const result_id = self.decl.fn_link.spirv.id;
397 const tv = self.decl.typed_value.most_recent.typed_value;
96398
97 switch (typed_value.ty.zigTypeTag()) {
98 .Fn => {
99 log.debug("Generating code for function '{s}'", .{ std.mem.spanZ(decl.name) });
399 if (tv.val.castTag(.function)) |func_payload| {
400 std.debug.assert(tv.ty.zigTypeTag() == .Fn);
401 const prototype_id = try self.getOrGenType(tv.ty);
402 try writeInstruction(&self.spv.fn_decls, .OpFunction, &[_]u32{
403 self.types.get(tv.ty.fnReturnType()).?, // This type should be generated along with the prototype.
404 result_id,
405 @bitCast(u32, spec.FunctionControl{}), // TODO: We can set inline here if the type requires it.
406 prototype_id,
407 });
100408
101 _ = try self.getOrGenType(typed_value.ty.fnReturnType());
102 },
103 else => return error.TODO,
409 const params = tv.ty.fnParamLen();
410 var i: usize = 0;
411
412 try self.args.ensureCapacity(params);
413 while (i < params) : (i += 1) {
414 const param_type_id = self.types.get(tv.ty.fnParamType(i)).?;
415 const arg_result_id = self.spv.allocResultId();
416 try writeInstruction(&self.spv.fn_decls, .OpFunctionParameter, &[_]u32{ param_type_id, arg_result_id });
417 self.args.appendAssumeCapacity(arg_result_id);
418 }
419
420 // TODO: This could probably be done in a better way...
421 const root_block_id = self.spv.allocResultId();
422 _ = try writeInstruction(&self.spv.fn_decls, .OpLabel, &[_]u32{root_block_id});
423 try self.genBody(func_payload.data.body);
424
425 try writeInstruction(&self.spv.fn_decls, .OpFunctionEnd, &[_]u32{});
426 } else {
427 return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: generate decl type {}", .{ tv.ty.zigTypeTag() });
428 }
429 }
430
431 fn genBody(self: *DeclGen, body: ir.Body) !void {
432 for (body.instructions) |inst| {
433 const maybe_result_id = try self.genInst(inst);
434 if (maybe_result_id) |result_id|
435 try self.values.putNoClobber(inst, result_id);
104436 }
105437 }
438
439 fn genInst(self: *DeclGen, inst: *Inst) !?u32 {
440 return switch (inst.tag) {
441 .add, .addwrap => try self.genBinOp(inst.castTag(.add).?),
442 .sub, .subwrap => try self.genBinOp(inst.castTag(.sub).?),
443 .mul, .mulwrap => try self.genBinOp(inst.castTag(.mul).?),
444 .div => try self.genBinOp(inst.castTag(.div).?),
445 .bit_and => try self.genBinOp(inst.castTag(.bit_and).?),
446 .bit_or => try self.genBinOp(inst.castTag(.bit_or).?),
447 .xor => try self.genBinOp(inst.castTag(.xor).?),
448 .cmp_eq => try self.genBinOp(inst.castTag(.cmp_eq).?),
449 .cmp_neq => try self.genBinOp(inst.castTag(.cmp_neq).?),
450 .cmp_gt => try self.genBinOp(inst.castTag(.cmp_gt).?),
451 .cmp_gte => try self.genBinOp(inst.castTag(.cmp_gte).?),
452 .cmp_lt => try self.genBinOp(inst.castTag(.cmp_lt).?),
453 .cmp_lte => try self.genBinOp(inst.castTag(.cmp_lte).?),
454 .bool_and => try self.genBinOp(inst.castTag(.bool_and).?),
455 .bool_or => try self.genBinOp(inst.castTag(.bool_or).?),
456 .not => try self.genUnOp(inst.castTag(.not).?),
457 .arg => self.genArg(),
458 // TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them
459 // throughout the IR.
460 .breakpoint => null,
461 .dbg_stmt => null,
462 .ret => self.genRet(inst.castTag(.ret).?),
463 .retvoid => self.genRetVoid(),
464 .unreach => self.genUnreach(),
465 else => self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement inst {}", .{inst.tag}),
466 };
467 }
468
469 fn genBinOp(self: *DeclGen, inst: *Inst.BinOp) !u32 {
470 // TODO: Will lhs and rhs have the same type?
471 const lhs_id = try self.resolve(inst.lhs);
472 const rhs_id = try self.resolve(inst.rhs);
473
474 const result_id = self.spv.allocResultId();
475 const result_type_id = try self.getOrGenType(inst.base.ty);
476
477 // TODO: Is the result the same as the argument types?
478 // This is supposed to be the case for SPIR-V.
479 std.debug.assert(inst.rhs.ty.eql(inst.lhs.ty));
480 std.debug.assert(inst.base.ty.tag() == .bool or inst.base.ty.eql(inst.lhs.ty));
481
482 // Binary operations are generally applicable to both scalar and vector operations in SPIR-V, but int and float
483 // versions of operations require different opcodes.
484 // For operations which produce bools, the information of inst.base.ty is not useful, so just pick either operand
485 // instead.
486 const info = try self.arithmeticTypeInfo(inst.lhs.ty);
487
488 if (info.class == .composite_integer)
489 return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: binary operations for composite integers", .{});
490
491 const is_bool = info.class == .bool;
492 const is_float = info.class == .float;
493 const is_signed = info.signedness == .signed;
494 // **Note**: All these operations must be valid for vectors of floats, integers and bools as well!
495 // For floating points, we generally want ordered operations (which return false if either operand is nan).
496 const opcode = switch (inst.base.tag) {
497 // The regular integer operations are all defined for wrapping. Since theyre only relevant for integers,
498 // we can just switch on both cases here.
499 .add, .addwrap => if (is_float) Opcode.OpFAdd else Opcode.OpIAdd,
500 .sub, .subwrap => if (is_float) Opcode.OpFSub else Opcode.OpISub,
501 .mul, .mulwrap => if (is_float) Opcode.OpFMul else Opcode.OpIMul,
502 // TODO: Trap if divisor is 0?
503 // TODO: Figure out of OpSDiv for unsigned/OpUDiv for signed does anything useful.
504 // => Those are probably for divTrunc and divFloor, though the compiler does not yet generate those.
505 // => TODO: Figure out how those work on the SPIR-V side.
506 // => TODO: Test these.
507 .div => if (is_float) Opcode.OpFDiv else if (is_signed) Opcode.OpSDiv else Opcode.OpUDiv,
508 // Only integer versions for these.
509 .bit_and => Opcode.OpBitwiseAnd,
510 .bit_or => Opcode.OpBitwiseOr,
511 .xor => Opcode.OpBitwiseXor,
512 // Int/bool/float -> bool operations.
513 .cmp_eq => if (is_float) Opcode.OpFOrdEqual else if (is_bool) Opcode.OpLogicalEqual else Opcode.OpIEqual,
514 .cmp_neq => if (is_float) Opcode.OpFOrdNotEqual else if (is_bool) Opcode.OpLogicalNotEqual else Opcode.OpINotEqual,
515 // Int/float -> bool operations.
516 // TODO: Verify that these OpFOrd type operations produce the right value.
517 // TODO: Is there a more fundamental difference between OpU and OpS operations here than just the type?
518 .cmp_gt => if (is_float) Opcode.OpFOrdGreaterThan else if (is_signed) Opcode.OpSGreaterThan else Opcode.OpUGreaterThan,
519 .cmp_gte => if (is_float) Opcode.OpFOrdGreaterThanEqual else if (is_signed) Opcode.OpSGreaterThanEqual else Opcode.OpUGreaterThanEqual,
520 .cmp_lt => if (is_float) Opcode.OpFOrdLessThan else if (is_signed) Opcode.OpSLessThan else Opcode.OpULessThan,
521 .cmp_lte => if (is_float) Opcode.OpFOrdLessThanEqual else if (is_signed) Opcode.OpSLessThanEqual else Opcode.OpULessThanEqual,
522 // Bool -> bool operations.
523 .bool_and => Opcode.OpLogicalAnd,
524 .bool_or => Opcode.OpLogicalOr,
525 else => unreachable,
526 };
527
528 try writeInstruction(&self.spv.fn_decls, opcode, &[_]u32{ result_type_id, result_id, lhs_id, rhs_id });
529
530 // TODO: Trap on overflow? Probably going to be annoying.
531 // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap.
532
533 if (info.class != .strange_integer)
534 return result_id;
535
536 return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: strange integer operation mask", .{});
537 }
538
539 fn genUnOp(self: *DeclGen, inst: *Inst.UnOp) !u32 {
540 const operand_id = try self.resolve(inst.operand);
541
542 const result_id = self.spv.allocResultId();
543 const result_type_id = try self.getOrGenType(inst.base.ty);
544
545 const info = try self.arithmeticTypeInfo(inst.operand.ty);
546
547 const opcode = switch (inst.base.tag) {
548 // Bool -> bool
549 .not => Opcode.OpLogicalNot,
550 else => unreachable,
551 };
552
553 try writeInstruction(&self.spv.fn_decls, opcode, &[_]u32{ result_type_id, result_id, operand_id });
554
555 return result_id;
556 }
557
558 fn genArg(self: *DeclGen) u32 {
559 defer self.next_arg_index += 1;
560 return self.args.items[self.next_arg_index];
561 }
562
563 fn genRet(self: *DeclGen, inst: *Inst.UnOp) !?u32 {
564 const operand_id = try self.resolve(inst.operand);
565 // TODO: This instruction needs to be the last in a block. Is that guaranteed?
566 try writeInstruction(&self.spv.fn_decls, .OpReturnValue, &[_]u32{ operand_id });
567 return null;
568 }
569
570 fn genRetVoid(self: *DeclGen) !?u32 {
571 // TODO: This instruction needs to be the last in a block. Is that guaranteed?
572 try writeInstruction(&self.spv.fn_decls, .OpReturn, &[_]u32{});
573 return null;
574 }
575
576 fn genUnreach(self: *DeclGen) !?u32 {
577 // TODO: This instruction needs to be the last in a block. Is that guaranteed?
578 try writeInstruction(&self.spv.fn_decls, .OpUnreachable, &[_]u32{});
579 return null;
580 }
106581};
src/link/SpirV.zig+36-7
......@@ -118,8 +118,8 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
118118 const module = self.base.options.module.?;
119119 const target = comp.getTarget();
120120
121 var spirv_module = codegen.SPIRVModule.init(target, self.base.allocator);
122 defer spirv_module.deinit();
121 var spv = codegen.SPIRVModule.init(self.base.allocator);
122 defer spv.deinit();
123123
124124 // Allocate an ID for every declaration before generating code,
125125 // so that we can access them before processing them.
......@@ -132,19 +132,48 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
132132 if (decl.typed_value != .most_recent)
133133 continue;
134134
135 decl.fn_link.spirv.id = spirv_module.allocResultId();
135 decl.fn_link.spirv.id = spv.allocResultId();
136136 log.debug("Allocating id {} to '{s}'", .{ decl.fn_link.spirv.id, std.mem.spanZ(decl.name) });
137137 }
138138 }
139139
140140 // Now, actually generate the code for all declarations.
141141 {
142 // We are just going to re-use this same DeclGen for every Decl, and we are just going to
143 // change the decl. Otherwise, we would have to keep a separate `args` and `types`, and re-construct this
144 // structure every time.
145 var decl_gen = codegen.DeclGen{
146 .module = module,
147 .spv = &spv,
148 .args = std.ArrayList(u32).init(self.base.allocator),
149 .next_arg_index = undefined,
150 .types = codegen.TypeMap.init(self.base.allocator),
151 .values = codegen.ValueMap.init(self.base.allocator),
152 .decl = undefined,
153 .error_msg = undefined,
154 };
155
156 defer decl_gen.values.deinit();
157 defer decl_gen.types.deinit();
158 defer decl_gen.args.deinit();
159
142160 for (module.decl_table.items()) |entry| {
143161 const decl = entry.value;
144162 if (decl.typed_value != .most_recent)
145163 continue;
146164
147 try spirv_module.gen(decl);
165 decl_gen.args.items.len = 0;
166 decl_gen.next_arg_index = 0;
167 decl_gen.decl = decl;
168 decl_gen.error_msg = null;
169
170 decl_gen.gen() catch |err| switch (err) {
171 error.AnalysisFail => {
172 try module.failed_decls.put(module.gpa, decl, decl_gen.error_msg.?);
173 return;
174 },
175 else => |e| return e,
176 };
148177 }
149178 }
150179
......@@ -155,7 +184,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
155184 spec.magic_number,
156185 (spec.version.major << 16) | (spec.version.minor << 8),
157186 0, // TODO: Register Zig compiler magic number.
158 spirv_module.resultIdBound(), // ID bound.
187 spv.resultIdBound(), // ID bound.
159188 0, // Schema (currently reserved for future use in the SPIR-V spec).
160189 });
161190
......@@ -166,8 +195,8 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
166195 // follows the SPIR-V logical module format!
167196 var all_buffers = [_]std.os.iovec_const{
168197 wordsToIovConst(binary.items),
169 wordsToIovConst(spirv_module.types_and_globals.items),
170 wordsToIovConst(spirv_module.fn_decls.items),
198 wordsToIovConst(spv.types_globals_constants.items),
199 wordsToIovConst(spv.fn_decls.items),
171200 };
172201
173202 const file = self.base.file.?;