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 @@...@@ -1,9 +1,13 @@
1const std = @import("std");1const std = @import("std");
2const Allocator = std.mem.Allocator;2const Allocator = std.mem.Allocator;
3const log = std.log.scoped(.codegen);
34
4const spec = @import("spirv/spec.zig");5const spec = @import("spirv/spec.zig");
5const Module = @import("../Module.zig");6const Module = @import("../Module.zig");
6const Decl = Module.Decl;7const 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
8pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void {12pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void {
9 const word_count = @intCast(u32, args.len + 1);13 const word_count = @intCast(u32, args.len + 1);
...@@ -12,38 +16,91 @@ pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []c...@@ -12,38 +16,91 @@ pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []c
12}16}
1317
14pub const SPIRVModule = struct {18pub const SPIRVModule = struct {
15 next_id: u32 = 0,19 next_result_id: u32 = 0,
16 free_id_list: std.ArrayList(u32),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 {
19 return .{29 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),
21 };34 };
22 }35 }
2336
24 pub fn deinit(self: *SPIRVModule) void {37 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;
26 }42 }
2743
28 pub fn allocId(self: *SPIRVModule) u32 {44 pub fn allocResultId(self: *SPIRVModule) u32 {
29 if (self.free_id_list.popOrNull()) |id| return id;45 defer self.next_result_id += 1;
46 return self.next_result_id;
47 }
3048
31 defer self.next_id += 1;49 pub fn resultIdBound(self: *SPIRVModule) u32 {
32 return self.next_id;50 return self.next_result_id;
33 }51 }
3452
35 pub fn freeId(self: *SPIRVModule, id: u32) void {53 pub fn getOrGenType(self: *SPIRVModule, t: Type) !u32 {
36 if (id + 1 == self.next_id) {54 // We can't use getOrPut here so we can recursively generate types.
37 self.next_id -= 1;55 if (self.types.get(t)) |already_generated| {
38 } else {56 return already_generated;
39 // If no more memory to append the id to the free list, just ignore it.
40 self.free_id_list.append(id) catch {};
41 }57 }
42 }
4358
44 pub fn idBound(self: *SPIRVModule) u32 {59 const result = self.allocResultId();
45 return self.next_id;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;
46 }92 }
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 }
49};106};
src/link/SpirV.zig+56-63
...@@ -16,11 +16,16 @@...@@ -16,11 +16,16 @@
16//! All function declarations without a body (extern functions presumably).16//! All function declarations without a body (extern functions presumably).
17//! All regular functions.17//! 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
19const SpirV = @This();23const SpirV = @This();
2024
21const std = @import("std");25const std = @import("std");
22const Allocator = std.mem.Allocator;26const Allocator = std.mem.Allocator;
23const assert = std.debug.assert;27const assert = std.debug.assert;
28const log = std.log.scoped(.link);
2429
25const Module = @import("../Module.zig");30const Module = @import("../Module.zig");
26const Compilation = @import("../Compilation.zig");31const Compilation = @import("../Compilation.zig");
...@@ -30,16 +35,15 @@ const trace = @import("../tracy.zig").trace;...@@ -30,16 +35,15 @@ const trace = @import("../tracy.zig").trace;
30const build_options = @import("build_options");35const build_options = @import("build_options");
31const spec = @import("../codegen/spirv/spec.zig");36const 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?
33pub const FnData = struct {39pub const FnData = struct {
34 id: ?u32 = null,40 // We're going to fill these in flushModule, and we're going to fill them unconditionally,
35 code: std.ArrayListUnmanaged(u32) = .{},41 // so just set it to undefined.
42 id: u32 = undefined
36};43};
3744
38base: link.File,45base: link.File,
3946
40// TODO: Does this file need to support multiple independent modules?
41spirv_module: codegen.SPIRVModule,
42
43pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {47pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
44 const spirv = try gpa.create(SpirV);48 const spirv = try gpa.create(SpirV);
45 spirv.* = .{49 spirv.* = .{
...@@ -49,7 +53,6 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {...@@ -49,7 +53,6 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
49 .file = null,53 .file = null,
50 .allocator = gpa,54 .allocator = gpa,
51 },55 },
52 .spirv_module = codegen.SPIRVModule.init(gpa),
53 };56 };
5457
55 // TODO: Figure out where to put all of these58 // 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...@@ -87,28 +90,9 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
87 return spirv;90 return spirv;
88}91}
8992
90pub fn deinit(self: *SpirV) void {93pub 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();
10894
109 // Free excess allocated memory for this Decl.95pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !void {}
110 fn_data.code.shrinkAndFree(self.base.allocator, fn_data.code.items.len);
111}
11296
113pub fn updateDeclExports(97pub fn updateDeclExports(
114 self: *SpirV,98 self: *SpirV,
...@@ -117,12 +101,7 @@ pub fn updateDeclExports(...@@ -117,12 +101,7 @@ pub fn updateDeclExports(
117 exports: []const *Module.Export,101 exports: []const *Module.Export,
118) !void {}102) !void {}
119103
120pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void {104pub 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}
126105
127pub fn flush(self: *SpirV, comp: *Compilation) !void {106pub fn flush(self: *SpirV, comp: *Compilation) !void {
128 if (build_options.have_llvm and self.base.options.use_lld) {107 if (build_options.have_llvm and self.base.options.use_lld) {
...@@ -139,55 +118,69 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {...@@ -139,55 +118,69 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
139 const module = self.base.options.module.?;118 const module = self.base.options.module.?;
140 const target = comp.getTarget();119 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
142 var binary = std.ArrayList(u32).init(self.base.allocator);151 var binary = std.ArrayList(u32).init(self.base.allocator);
143 defer binary.deinit();152 defer binary.deinit();
144153
145 // Note: The order of adding sections to the final binary
146 // follows the SPIR-V logical module format!
147
148 try binary.appendSlice(&[_]u32{154 try binary.appendSlice(&[_]u32{
149 spec.magic_number,155 spec.magic_number,
150 (spec.version.major << 16) | (spec.version.minor << 8),156 (spec.version.major << 16) | (spec.version.minor << 8),
151 0, // TODO: Register Zig compiler magic number.157 0, // TODO: Register Zig compiler magic number.
152 self.spirv_module.idBound(),158 spirv_module.resultIdBound(), // ID bound.
153 0, // Schema (currently reserved for future use in the SPIR-V spec).159 0, // Schema (currently reserved for future use in the SPIR-V spec).
154 });160 });
155161
156 try writeCapabilities(&binary, target);162 try writeCapabilities(&binary, target);
157 try writeMemoryModel(&binary, target);163 try writeMemoryModel(&binary, target);
158164
159 // Collect list of buffers to write.165 // Note: The order of adding sections to the final binary
160 // SPIR-V files support both little and big endian words. The actual format is166 // follows the SPIR-V logical module format!
161 // disambiguated by the magic number, and so theoretically we don't need to worry167 var all_buffers = [_]std.os.iovec_const{
162 // about endian-ness when writing the final binary.168 wordsToIovConst(binary.items),
163 var all_buffers = std.ArrayList(std.os.iovec_const).init(self.base.allocator);169 wordsToIovConst(spirv_module.types_and_globals.items),
164 defer all_buffers.deinit();170 wordsToIovConst(spirv_module.fn_decls.items),
165171 };
166 // Pre-allocate enough for the binary info + all functions172
167 try all_buffers.ensureCapacity(module.decl_table.count() + 1);173 const file = self.base.file.?;
168174 const bytes = std.mem.sliceAsBytes(binary.items);
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 }
181175
182 var file_size: u64 = 0;176 var file_size: u64 = 0;
183 for (all_buffers.items) |iov| {177 for (all_buffers) |iov| {
184 file_size += iov.iov_len;178 file_size += iov.iov_len;
185 }179 }
186180
187 const file = self.base.file.?;
188 try file.seekTo(0);181 try file.seekTo(0);
189 try file.setEndPos(file_size);182 try file.setEndPos(file_size);
190 try file.pwritevAll(all_buffers.items, 0);183 try file.pwritevAll(&all_buffers, 0);
191}184}
192185
193fn writeCapabilities(binary: *std.ArrayList(u32), target: std.Target) !void {186fn writeCapabilities(binary: *std.ArrayList(u32), target: std.Target) !void {
...@@ -231,4 +224,4 @@ fn wordsToIovConst(words: []const u32) std.os.iovec_const {...@@ -231,4 +224,4 @@ fn wordsToIovConst(words: []const u32) std.os.iovec_const {
231 .iov_base = bytes.ptr,224 .iov_base = bytes.ptr,
232 .iov_len = bytes.len,225 .iov_len = bytes.len,
233 };226 };
234}227}
\ No newline at end of file
tools/gen_spirv_spec.zig+7-10
...@@ -118,11 +118,16 @@ pub fn main() !void {...@@ -118,11 +118,16 @@ pub fn main() !void {
118}118}
119119
120fn render(writer: Writer, registry: Registry) !void {120fn 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
121 switch (registry) {128 switch (registry) {
122 .core => |core_reg| {129 .core => |core_reg| {
123 try renderCopyRight(writer, core_reg.copyright);
124 try writer.print(130 try writer.print(
125 \\const Version = @import("builtin").Version;
126 \\pub const version = Version{{.major = {}, .minor = {}, .patch = {}}};131 \\pub const version = Version{{.major = {}, .minor = {}, .patch = {}}};
127 \\pub const magic_number: u32 = {s};132 \\pub const magic_number: u32 = {s};
128 \\133 \\
...@@ -132,9 +137,7 @@ fn render(writer: Writer, registry: Registry) !void {...@@ -132,9 +137,7 @@ fn render(writer: Writer, registry: Registry) !void {
132 try renderOperandKinds(writer, core_reg.operand_kinds);137 try renderOperandKinds(writer, core_reg.operand_kinds);
133 },138 },
134 .extension => |ext_reg| {139 .extension => |ext_reg| {
135 try renderCopyRight(writer, ext_reg.copyright);
136 try writer.print(140 try writer.print(
137 \\const Version = @import("builtin").Version;
138 \\pub const version = Version{{.major = {}, .minor = 0, .patch = {}}};141 \\pub const version = Version{{.major = {}, .minor = 0, .patch = {}}};
139 \\142 \\
140 , .{ ext_reg.version, ext_reg.revision },143 , .{ ext_reg.version, ext_reg.revision },
...@@ -145,12 +148,6 @@ fn render(writer: Writer, registry: Registry) !void {...@@ -145,12 +148,6 @@ fn render(writer: Writer, registry: Registry) !void {
145 }148 }
146}149}
147150
148fn renderCopyRight(writer: Writer, copyright: []const []const u8) !void {
149 for (copyright) |line| {
150 try writer.print("// {s}\n", .{ line });
151 }
152}
153
154fn renderOpcodes(writer: Writer, instructions: []const Instruction) !void {151fn renderOpcodes(writer: Writer, instructions: []const Instruction) !void {
155 try writer.writeAll("pub const Opcode = extern enum(u16) {\n");152 try writer.writeAll("pub const Opcode = extern enum(u16) {\n");
156 for (instructions) |instr| {153 for (instructions) |instr| {