authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-05 01:59:23+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-14 19:49:32+02:00
logd45e7dfc241f917946e057ad67d291bf1f0028e0
tree1896e9c830bc746a1f8e606ef3eeaaf1596aa5ea
parentfa3afede5809cef6c1d5856c1f930344181c16c8

SPIR-V: Begin generating types


3 files changed, 139 insertions(+), 92 deletions(-)

src/codegen/spirv.zig+76-19
......@@ -1,9 +1,13 @@
11const std = @import("std");
22const Allocator = std.mem.Allocator;
3const log = std.log.scoped(.codegen);
34
45const spec = @import("spirv/spec.zig");
56const Module = @import("../Module.zig");
67const Decl = Module.Decl;
8const Type = @import("../type.zig").Type;
9
10pub const TypeMap = std.HashMap(Type, u32, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
711
812pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void {
913 const word_count = @intCast(u32, args.len + 1);
......@@ -12,38 +16,91 @@ pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []c
1216}
1317
1418pub const SPIRVModule = struct {
15 next_id: u32 = 0,
16 free_id_list: std.ArrayList(u32),
19 next_result_id: u32 = 0,
20
21 target: std.Target,
22
23 types: TypeMap,
24
25 types_and_globals: std.ArrayList(u32),
26 fn_decls: std.ArrayList(u32),
1727
18 pub fn init(allocator: *Allocator) SPIRVModule {
28 pub fn init(target: std.Target, allocator: *Allocator) SPIRVModule {
1929 return .{
20 .free_id_list = std.ArrayList(u32).init(allocator),
30 .target = target,
31 .types = TypeMap.init(allocator),
32 .types_and_globals = std.ArrayList(u32).init(allocator),
33 .fn_decls = std.ArrayList(u32).init(allocator),
2134 };
2235 }
2336
2437 pub fn deinit(self: *SPIRVModule) void {
25 self.free_id_list.deinit();
38 self.fn_decls.deinit();
39 self.types_and_globals.deinit();
40 self.types.deinit();
41 self.* = undefined;
2642 }
2743
28 pub fn allocId(self: *SPIRVModule) u32 {
29 if (self.free_id_list.popOrNull()) |id| return id;
44 pub fn allocResultId(self: *SPIRVModule) u32 {
45 defer self.next_result_id += 1;
46 return self.next_result_id;
47 }
3048
31 defer self.next_id += 1;
32 return self.next_id;
49 pub fn resultIdBound(self: *SPIRVModule) u32 {
50 return self.next_result_id;
3351 }
3452
35 pub fn freeId(self: *SPIRVModule, id: u32) void {
36 if (id + 1 == self.next_id) {
37 self.next_id -= 1;
38 } else {
39 // If no more memory to append the id to the free list, just ignore it.
40 self.free_id_list.append(id) catch {};
53 pub fn getOrGenType(self: *SPIRVModule, t: Type) !u32 {
54 // We can't use getOrPut here so we can recursively generate types.
55 if (self.types.get(t)) |already_generated| {
56 return already_generated;
4157 }
42 }
4358
44 pub fn idBound(self: *SPIRVModule) u32 {
45 return self.next_id;
59 const result = self.allocResultId();
60
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 }),
64 .Int => {
65 const int_info = t.intInfo(self.target);
66 try writeInstruction(&self.types_and_globals, .OpTypeInt, &[_]u32{
67 result,
68 int_info.bits,
69 switch (int_info.signedness) {
70 .unsigned => 0,
71 .signed => 1,
72 },
73 });
74 },
75 // TODO: Verify that floatBits() will be correct.
76 .Float => try writeInstruction(&self.types_and_globals, .OpTypeFloat, &[_]u32{ result, t.floatBits(self.target) }),
77 .Null,
78 .Undefined,
79 .EnumLiteral,
80 .ComptimeFloat,
81 .ComptimeInt,
82 .Type,
83 => unreachable, // Must be const or comptime.
84
85 .BoundFn => unreachable, // this type will be deleted from the language.
86
87 else => return error.TODO,
88 }
89
90 try self.types.put(t, result);
91 return result;
4692 }
4793
48 pub fn genDecl(self: SPIRVModule, id: u32, code: *std.ArrayList(u32), decl: *Decl) !void {}
94 pub fn gen(self: *SPIRVModule, decl: *Decl) !void {
95 const typed_value = decl.typed_value.most_recent.typed_value;
96
97 switch (typed_value.ty.zigTypeTag()) {
98 .Fn => {
99 log.debug("Generating code for function '{s}'", .{ std.mem.spanZ(decl.name) });
100
101 _ = try self.getOrGenType(typed_value.ty.fnReturnType());
102 },
103 else => return error.TODO,
104 }
105 }
49106};
src/link/SpirV.zig+56-63
......@@ -16,11 +16,16 @@
1616//! All function declarations without a body (extern functions presumably).
1717//! All regular functions.
1818
19// Because SPIR-V requires re-compilation anyway, and so hot swapping will not work
20// anyway, we simply generate all the code in flushModule. This keeps
21// things considerably simpler.
22
1923const SpirV = @This();
2024
2125const std = @import("std");
2226const Allocator = std.mem.Allocator;
2327const assert = std.debug.assert;
28const log = std.log.scoped(.link);
2429
2530const Module = @import("../Module.zig");
2631const Compilation = @import("../Compilation.zig");
......@@ -30,16 +35,15 @@ const trace = @import("../tracy.zig").trace;
3035const build_options = @import("build_options");
3136const spec = @import("../codegen/spirv/spec.zig");
3237
38// TODO: Should this struct be used at all rather than just a hashmap of aux data for every decl?
3339pub const FnData = struct {
34 id: ?u32 = null,
35 code: std.ArrayListUnmanaged(u32) = .{},
40 // We're going to fill these in flushModule, and we're going to fill them unconditionally,
41 // so just set it to undefined.
42 id: u32 = undefined
3643};
3744
3845base: link.File,
3946
40// TODO: Does this file need to support multiple independent modules?
41spirv_module: codegen.SPIRVModule,
42
4347pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
4448 const spirv = try gpa.create(SpirV);
4549 spirv.* = .{
......@@ -49,7 +53,6 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
4953 .file = null,
5054 .allocator = gpa,
5155 },
52 .spirv_module = codegen.SPIRVModule.init(gpa),
5356 };
5457
5558 // TODO: Figure out where to put all of these
......@@ -87,28 +90,9 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
8790 return spirv;
8891}
8992
90pub fn deinit(self: *SpirV) void {
91 self.spirv_module.deinit();
92}
93
94pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !void {
95 const tracy = trace(@src());
96 defer tracy.end();
97
98 const fn_data = &decl.fn_link.spirv;
99 if (fn_data.id == null) {
100 fn_data.id = self.spirv_module.allocId();
101 }
102
103 var managed_code = fn_data.code.toManaged(self.base.allocator);
104 managed_code.items.len = 0;
105
106 try self.spirv_module.genDecl(fn_data.id.?, &managed_code, decl);
107 fn_data.code = managed_code.toUnmanaged();
93pub fn deinit(self: *SpirV) void {}
10894
109 // Free excess allocated memory for this Decl.
110 fn_data.code.shrinkAndFree(self.base.allocator, fn_data.code.items.len);
111}
95pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !void {}
11296
11397pub fn updateDeclExports(
11498 self: *SpirV,
......@@ -117,12 +101,7 @@ pub fn updateDeclExports(
117101 exports: []const *Module.Export,
118102) !void {}
119103
120pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void {
121 var fn_data = decl.fn_link.spirv;
122 fn_data.code.deinit(self.base.allocator);
123 if (fn_data.id) |id| self.spirv_module.freeId(id);
124 decl.fn_link.spirv = undefined;
125}
104pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void {}
126105
127106pub fn flush(self: *SpirV, comp: *Compilation) !void {
128107 if (build_options.have_llvm and self.base.options.use_lld) {
......@@ -139,55 +118,69 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
139118 const module = self.base.options.module.?;
140119 const target = comp.getTarget();
141120
121 var spirv_module = codegen.SPIRVModule.init(target, self.base.allocator);
122 defer spirv_module.deinit();
123
124 // Allocate an ID for every declaration before generating code,
125 // so that we can access them before processing them.
126 // TODO: We're allocating an ID unconditionally now, are there
127 // declarations which don't generate a result?
128 // TODO: fn_link is used here, but thats probably not the right field. It will work anyway though.
129 {
130 for (module.decl_table.items()) |entry| {
131 const decl = entry.value;
132 if (decl.typed_value != .most_recent)
133 continue;
134
135 decl.fn_link.spirv.id = spirv_module.allocResultId();
136 log.debug("Allocating id {} to '{s}'", .{ decl.fn_link.spirv.id, std.mem.spanZ(decl.name) });
137 }
138 }
139
140 // Now, actually generate the code for all declarations.
141 {
142 for (module.decl_table.items()) |entry| {
143 const decl = entry.value;
144 if (decl.typed_value != .most_recent)
145 continue;
146
147 try spirv_module.gen(decl);
148 }
149 }
150
142151 var binary = std.ArrayList(u32).init(self.base.allocator);
143152 defer binary.deinit();
144153
145 // Note: The order of adding sections to the final binary
146 // follows the SPIR-V logical module format!
147
148154 try binary.appendSlice(&[_]u32{
149155 spec.magic_number,
150156 (spec.version.major << 16) | (spec.version.minor << 8),
151157 0, // TODO: Register Zig compiler magic number.
152 self.spirv_module.idBound(),
158 spirv_module.resultIdBound(), // ID bound.
153159 0, // Schema (currently reserved for future use in the SPIR-V spec).
154160 });
155161
156162 try writeCapabilities(&binary, target);
157163 try writeMemoryModel(&binary, target);
158164
159 // Collect list of buffers to write.
160 // SPIR-V files support both little and big endian words. The actual format is
161 // disambiguated by the magic number, and so theoretically we don't need to worry
162 // about endian-ness when writing the final binary.
163 var all_buffers = std.ArrayList(std.os.iovec_const).init(self.base.allocator);
164 defer all_buffers.deinit();
165
166 // Pre-allocate enough for the binary info + all functions
167 try all_buffers.ensureCapacity(module.decl_table.count() + 1);
168
169 all_buffers.appendAssumeCapacity(wordsToIovConst(binary.items));
170
171 for (module.decl_table.items()) |entry| {
172 const decl = entry.value;
173 switch (decl.typed_value) {
174 .most_recent => |tvm| {
175 const fn_data = &decl.fn_link.spirv;
176 all_buffers.appendAssumeCapacity(wordsToIovConst(fn_data.code.items));
177 },
178 .never_succeeded => continue,
179 }
180 }
165 // Note: The order of adding sections to the final binary
166 // follows the SPIR-V logical module format!
167 var all_buffers = [_]std.os.iovec_const{
168 wordsToIovConst(binary.items),
169 wordsToIovConst(spirv_module.types_and_globals.items),
170 wordsToIovConst(spirv_module.fn_decls.items),
171 };
172
173 const file = self.base.file.?;
174 const bytes = std.mem.sliceAsBytes(binary.items);
181175
182176 var file_size: u64 = 0;
183 for (all_buffers.items) |iov| {
177 for (all_buffers) |iov| {
184178 file_size += iov.iov_len;
185179 }
186180
187 const file = self.base.file.?;
188181 try file.seekTo(0);
189182 try file.setEndPos(file_size);
190 try file.pwritevAll(all_buffers.items, 0);
183 try file.pwritevAll(&all_buffers, 0);
191184}
192185
193186fn writeCapabilities(binary: *std.ArrayList(u32), target: std.Target) !void {
......@@ -231,4 +224,4 @@ fn wordsToIovConst(words: []const u32) std.os.iovec_const {
231224 .iov_base = bytes.ptr,
232225 .iov_len = bytes.len,
233226 };
234}
227}
\ No newline at end of file
tools/gen_spirv_spec.zig+7-10
......@@ -118,11 +118,16 @@ pub fn main() !void {
118118}
119119
120120fn render(writer: Writer, registry: Registry) !void {
121 try writer.writeAll(
122 \\//! This file is auto-generated by tools/gen_spirv_spec.zig.
123 \\
124 \\const Version = @import("builtin").Version;
125 \\
126 );
127
121128 switch (registry) {
122129 .core => |core_reg| {
123 try renderCopyRight(writer, core_reg.copyright);
124130 try writer.print(
125 \\const Version = @import("builtin").Version;
126131 \\pub const version = Version{{.major = {}, .minor = {}, .patch = {}}};
127132 \\pub const magic_number: u32 = {s};
128133 \\
......@@ -132,9 +137,7 @@ fn render(writer: Writer, registry: Registry) !void {
132137 try renderOperandKinds(writer, core_reg.operand_kinds);
133138 },
134139 .extension => |ext_reg| {
135 try renderCopyRight(writer, ext_reg.copyright);
136140 try writer.print(
137 \\const Version = @import("builtin").Version;
138141 \\pub const version = Version{{.major = {}, .minor = 0, .patch = {}}};
139142 \\
140143 , .{ ext_reg.version, ext_reg.revision },
......@@ -145,12 +148,6 @@ fn render(writer: Writer, registry: Registry) !void {
145148 }
146149}
147150
148fn renderCopyRight(writer: Writer, copyright: []const []const u8) !void {
149 for (copyright) |line| {
150 try writer.print("// {s}\n", .{ line });
151 }
152}
153
154151fn renderOpcodes(writer: Writer, instructions: []const Instruction) !void {
155152 try writer.writeAll("pub const Opcode = extern enum(u16) {\n");
156153 for (instructions) |instr| {