Commit d45e7dfc24

Robin Voetter <robin@voetter.nl>
2021-05-05 01:59:23
SPIR-V: Begin generating types
1 parent fa3afed
Changed files (3)
src/codegen/spirv.zig
@@ -1,9 +1,13 @@
 const std = @import("std");
 const Allocator = std.mem.Allocator;
+const log = std.log.scoped(.codegen);
 
 const spec = @import("spirv/spec.zig");
 const Module = @import("../Module.zig");
 const Decl = Module.Decl;
+const Type = @import("../type.zig").Type;
+
+pub const TypeMap = std.HashMap(Type, u32, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
 
 pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void {
     const word_count = @intCast(u32, args.len + 1);
@@ -12,38 +16,91 @@ pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []c
 }
 
 pub const SPIRVModule = struct {
-    next_id: u32 = 0,
-    free_id_list: std.ArrayList(u32),
+    next_result_id: u32 = 0,
+
+    target: std.Target,
+
+    types: TypeMap,
+
+    types_and_globals: std.ArrayList(u32),
+    fn_decls: std.ArrayList(u32),
 
-    pub fn init(allocator: *Allocator) SPIRVModule {
+    pub fn init(target: std.Target, allocator: *Allocator) SPIRVModule {
         return .{
-            .free_id_list = std.ArrayList(u32).init(allocator),
+            .target = target,
+            .types = TypeMap.init(allocator),
+            .types_and_globals = std.ArrayList(u32).init(allocator),
+            .fn_decls = std.ArrayList(u32).init(allocator),
         };
     }
 
     pub fn deinit(self: *SPIRVModule) void {
-        self.free_id_list.deinit();
+        self.fn_decls.deinit();
+        self.types_and_globals.deinit();
+        self.types.deinit();
+        self.* = undefined;
     }
 
-    pub fn allocId(self: *SPIRVModule) u32 {
-        if (self.free_id_list.popOrNull()) |id| return id;
+    pub fn allocResultId(self: *SPIRVModule) u32 {
+        defer self.next_result_id += 1;
+        return self.next_result_id;
+    }
 
-        defer self.next_id += 1;
-        return self.next_id;
+    pub fn resultIdBound(self: *SPIRVModule) u32 {
+        return self.next_result_id;
     }
 
-    pub fn freeId(self: *SPIRVModule, id: u32) void {
-        if (id + 1 == self.next_id) {
-            self.next_id -= 1;
-        } else {
-            // If no more memory to append the id to the free list, just ignore it.
-            self.free_id_list.append(id) catch {};
+    pub fn getOrGenType(self: *SPIRVModule, t: Type) !u32 {
+        // We can't use getOrPut here so we can recursively generate types.
+        if (self.types.get(t)) |already_generated| {
+            return already_generated;
         }
-    }
 
-    pub fn idBound(self: *SPIRVModule) u32 {
-        return self.next_id;
+        const result = self.allocResultId();
+
+        switch (t.zigTypeTag()) {
+            .Void => try writeInstruction(&self.types_and_globals, .OpTypeVoid, &[_]u32{ result }),
+            .Bool => try writeInstruction(&self.types_and_globals, .OpTypeBool, &[_]u32{ result }),
+            .Int => {
+                const int_info = t.intInfo(self.target);
+                try writeInstruction(&self.types_and_globals, .OpTypeInt, &[_]u32{
+                    result,
+                    int_info.bits,
+                    switch (int_info.signedness) {
+                        .unsigned => 0,
+                        .signed => 1,
+                    },
+                });
+            },
+            // TODO: Verify that floatBits() will be correct.
+            .Float => try writeInstruction(&self.types_and_globals, .OpTypeFloat, &[_]u32{ result, t.floatBits(self.target) }),
+            .Null,
+            .Undefined,
+            .EnumLiteral,
+            .ComptimeFloat,
+            .ComptimeInt,
+            .Type,
+            => unreachable, // Must be const or comptime.
+
+            .BoundFn => unreachable, // this type will be deleted from the language.
+
+            else => return error.TODO,
+        }
+
+        try self.types.put(t, result);
+        return result;
     }
 
-    pub fn genDecl(self: SPIRVModule, id: u32, code: *std.ArrayList(u32), decl: *Decl) !void {}
+    pub fn gen(self: *SPIRVModule, decl: *Decl) !void {
+        const typed_value = decl.typed_value.most_recent.typed_value;
+
+        switch (typed_value.ty.zigTypeTag()) {
+            .Fn => {
+                log.debug("Generating code for function '{s}'", .{ std.mem.spanZ(decl.name) });
+
+                _ = try self.getOrGenType(typed_value.ty.fnReturnType());
+            },
+            else => return error.TODO,
+        }
+    }
 };
src/link/SpirV.zig
@@ -16,11 +16,16 @@
 //! All function declarations without a body (extern functions presumably).
 //! All regular functions.
 
+// Because SPIR-V requires re-compilation anyway, and so hot swapping will not work
+// anyway, we simply generate all the code in flushModule. This keeps
+// things considerably simpler.
+
 const SpirV = @This();
 
 const std = @import("std");
 const Allocator = std.mem.Allocator;
 const assert = std.debug.assert;
+const log = std.log.scoped(.link);
 
 const Module = @import("../Module.zig");
 const Compilation = @import("../Compilation.zig");
@@ -30,16 +35,15 @@ const trace = @import("../tracy.zig").trace;
 const build_options = @import("build_options");
 const spec = @import("../codegen/spirv/spec.zig");
 
+// TODO: Should this struct be used at all rather than just a hashmap of aux data for every decl?
 pub const FnData = struct {
-    id: ?u32 = null,
-    code: std.ArrayListUnmanaged(u32) = .{},
+    // We're going to fill these in flushModule, and we're going to fill them unconditionally,
+    // so just set it to undefined.
+    id: u32 = undefined
 };
 
 base: link.File,
 
-// TODO: Does this file need to support multiple independent modules?
-spirv_module: codegen.SPIRVModule,
-
 pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
     const spirv = try gpa.create(SpirV);
     spirv.* = .{
@@ -49,7 +53,6 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
             .file = null,
             .allocator = gpa,
         },
-        .spirv_module = codegen.SPIRVModule.init(gpa),
     };
 
     // 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
     return spirv;
 }
 
-pub fn deinit(self: *SpirV) void {
-    self.spirv_module.deinit();
-}
-
-pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const fn_data = &decl.fn_link.spirv;
-    if (fn_data.id == null) {
-        fn_data.id = self.spirv_module.allocId();
-    }
-
-    var managed_code = fn_data.code.toManaged(self.base.allocator);
-    managed_code.items.len = 0;
-
-    try self.spirv_module.genDecl(fn_data.id.?, &managed_code, decl);
-    fn_data.code = managed_code.toUnmanaged();
+pub fn deinit(self: *SpirV) void {}
 
-    // Free excess allocated memory for this Decl.
-    fn_data.code.shrinkAndFree(self.base.allocator, fn_data.code.items.len);
-}
+pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !void {}
 
 pub fn updateDeclExports(
     self: *SpirV,
@@ -117,12 +101,7 @@ pub fn updateDeclExports(
     exports: []const *Module.Export,
 ) !void {}
 
-pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void {
-    var fn_data = decl.fn_link.spirv;
-    fn_data.code.deinit(self.base.allocator);
-    if (fn_data.id) |id| self.spirv_module.freeId(id);
-    decl.fn_link.spirv = undefined;
-}
+pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void {}
 
 pub fn flush(self: *SpirV, comp: *Compilation) !void {
     if (build_options.have_llvm and self.base.options.use_lld) {
@@ -139,55 +118,69 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
     const module = self.base.options.module.?;
     const target = comp.getTarget();
 
+    var spirv_module = codegen.SPIRVModule.init(target, self.base.allocator);
+    defer spirv_module.deinit();
+
+    // Allocate an ID for every declaration before generating code,
+    // so that we can access them before processing them.
+    // TODO: We're allocating an ID unconditionally now, are there
+    // declarations which don't generate a result?
+    // TODO: fn_link is used here, but thats probably not the right field. It will work anyway though.
+    {
+        for (module.decl_table.items()) |entry| {
+            const decl = entry.value;
+            if (decl.typed_value != .most_recent)
+                continue;
+
+            decl.fn_link.spirv.id = spirv_module.allocResultId();
+            log.debug("Allocating id {} to '{s}'", .{ decl.fn_link.spirv.id, std.mem.spanZ(decl.name) });
+        }
+    }
+
+    // Now, actually generate the code for all declarations.
+    {
+        for (module.decl_table.items()) |entry| {
+            const decl = entry.value;
+            if (decl.typed_value != .most_recent)
+                continue;
+
+            try spirv_module.gen(decl);
+        }
+    }
+
     var binary = std.ArrayList(u32).init(self.base.allocator);
     defer binary.deinit();
 
-    // Note: The order of adding sections to the final binary
-    // follows the SPIR-V logical module format!
-
     try binary.appendSlice(&[_]u32{
         spec.magic_number,
         (spec.version.major << 16) | (spec.version.minor << 8),
         0, // TODO: Register Zig compiler magic number.
-        self.spirv_module.idBound(),
+        spirv_module.resultIdBound(), // ID bound.
         0, // Schema (currently reserved for future use in the SPIR-V spec).
     });
 
     try writeCapabilities(&binary, target);
     try writeMemoryModel(&binary, target);
 
-    // Collect list of buffers to write.
-    // SPIR-V files support both little and big endian words. The actual format is
-    // disambiguated by the magic number, and so theoretically we don't need to worry
-    // about endian-ness when writing the final binary.
-    var all_buffers = std.ArrayList(std.os.iovec_const).init(self.base.allocator);
-    defer all_buffers.deinit();
-
-    // Pre-allocate enough for the binary info + all functions
-    try all_buffers.ensureCapacity(module.decl_table.count() + 1);
-
-    all_buffers.appendAssumeCapacity(wordsToIovConst(binary.items));
-
-    for (module.decl_table.items()) |entry| {
-        const decl = entry.value;
-        switch (decl.typed_value) {
-            .most_recent => |tvm| {
-                const fn_data = &decl.fn_link.spirv;
-                all_buffers.appendAssumeCapacity(wordsToIovConst(fn_data.code.items));
-            },
-            .never_succeeded => continue,
-        }
-    }
+    // Note: The order of adding sections to the final binary
+    // follows the SPIR-V logical module format!
+    var all_buffers = [_]std.os.iovec_const{
+        wordsToIovConst(binary.items),
+        wordsToIovConst(spirv_module.types_and_globals.items),
+        wordsToIovConst(spirv_module.fn_decls.items),
+    };
+
+    const file = self.base.file.?;
+    const bytes = std.mem.sliceAsBytes(binary.items);
 
     var file_size: u64 = 0;
-    for (all_buffers.items) |iov| {
+    for (all_buffers) |iov| {
         file_size += iov.iov_len;
     }
 
-    const file = self.base.file.?;
     try file.seekTo(0);
     try file.setEndPos(file_size);
-    try file.pwritevAll(all_buffers.items, 0);
+    try file.pwritevAll(&all_buffers, 0);
 }
 
 fn writeCapabilities(binary: *std.ArrayList(u32), target: std.Target) !void {
@@ -231,4 +224,4 @@ fn wordsToIovConst(words: []const u32) std.os.iovec_const {
         .iov_base = bytes.ptr,
         .iov_len = bytes.len,
     };
-}
+}
\ No newline at end of file
tools/gen_spirv_spec.zig
@@ -118,11 +118,16 @@ pub fn main() !void {
 }
 
 fn render(writer: Writer, registry: Registry) !void {
+    try writer.writeAll(
+        \\//! This file is auto-generated by tools/gen_spirv_spec.zig.
+        \\
+        \\const Version = @import("builtin").Version;
+        \\
+    );
+
     switch (registry) {
         .core => |core_reg| {
-            try renderCopyRight(writer, core_reg.copyright);
             try writer.print(
-                \\const Version = @import("builtin").Version;
                 \\pub const version = Version{{.major = {}, .minor = {}, .patch = {}}};
                 \\pub const magic_number: u32 = {s};
                 \\
@@ -132,9 +137,7 @@ fn render(writer: Writer, registry: Registry) !void {
             try renderOperandKinds(writer, core_reg.operand_kinds);
         },
         .extension => |ext_reg| {
-            try renderCopyRight(writer, ext_reg.copyright);
             try writer.print(
-                \\const Version = @import("builtin").Version;
                 \\pub const version = Version{{.major = {}, .minor = 0, .patch = {}}};
                 \\
                 , .{ ext_reg.version, ext_reg.revision },
@@ -145,12 +148,6 @@ fn render(writer: Writer, registry: Registry) !void {
     }
 }
 
-fn renderCopyRight(writer: Writer, copyright: []const []const u8) !void {
-    for (copyright) |line| {
-        try writer.print("// {s}\n", .{ line });
-    }
-}
-
 fn renderOpcodes(writer: Writer, instructions: []const Instruction) !void {
     try writer.writeAll("pub const Opcode = extern enum(u16) {\n");
     for (instructions) |instr| {