Commit b2b87b5900

Robin Voetter <robin@voetter.nl>
2021-01-19 00:34:44
SPIR-V: Linking and codegen setup
1 parent ab607d4
Changed files (5)
src/codegen/spirv.zig
@@ -0,0 +1,22 @@
+const std = @import("std");
+const spec = @import("spirv/spec.zig");
+const Module = @import("../Module.zig");
+const Decl = Module.Decl;
+
+pub const SPIRVModule = struct {
+    // TODO: Also use a free list.
+    next_id: u32 = 0,
+
+    pub fn allocId(self: *SPIRVModule) u32 {
+        defer self.next_id += 1;
+        return self.next_id;
+    }
+
+    pub fn idBound(self: *SPIRVModule) u32 {
+        return self.next_id;
+    }
+
+    pub fn genDecl(self: SPIRVModule, id: u32, code: *std.ArrayList(u32), decl: *Decl) !void {
+
+    }
+};
src/link/SpirV.zig
@@ -0,0 +1,131 @@
+const SpirV = @This();
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
+
+const Module = @import("../Module.zig");
+const Compilation = @import("../Compilation.zig");
+const link = @import("../link.zig");
+const codegen = @import("../codegen/spirv.zig");
+const trace = @import("../tracy.zig").trace;
+const build_options = @import("build_options");
+const spec = @import("../codegen/spirv/spec.zig");
+
+pub const FnData = struct {
+    id: ?u32 = null,
+    code: std.ArrayListUnmanaged(u32) = .{},
+};
+
+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.* = .{
+        .base = .{
+            .tag = .spirv,
+            .options = options,
+            .file = null,
+            .allocator = gpa,
+        },
+    };
+    return spirv;
+}
+
+pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*SpirV {
+    assert(options.object_format == .spirv);
+
+    if (options.use_llvm) return error.LLVM_BackendIsTODO_ForSpirV; // TODO: LLVM Doesn't support SpirV at all.
+    if (options.use_lld) return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all.
+
+    // TODO: read the file and keep vaild parts instead of truncating
+    const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
+    errdefer file.close();
+
+    const spirv = try createEmpty(allocator, options);
+    errdefer spirv.base.destroy();
+
+    spirv.base.file = file;
+    return spirv;
+}
+
+pub fn deinit(self: *SpirV) void {
+}
+
+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();
+
+    // Free excess allocated memory for this Decl.
+    fn_data.code.shrinkAndFree(self.base.allocator, fn_data.code.items.len);
+}
+
+pub fn updateDeclExports(
+    self: *SpirV,
+    module: *Module,
+    decl: *const Module.Decl,
+    exports: []const *Module.Export,
+) !void {}
+
+pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void {
+    decl.fn_link.spirv.code.deinit(self.base.allocator);
+    decl.fn_link.spirv = undefined;
+}
+
+pub fn flush(self: *SpirV, comp: *Compilation) !void {
+    if (build_options.have_llvm and self.base.options.use_lld) {
+        return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all.
+    } else {
+        return self.flushModule(comp);
+    }
+}
+
+pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const module = self.base.options.module.?;
+
+    const file = self.base.file.?;
+    var bw = std.io.bufferedWriter(file.writer());
+    const writer = bw.writer();
+
+    // Header
+    // SPIR-V files support both little and big endian words. The actual format is disambiguated by
+    // the magic number. This backend uses little endian.
+    try writer.writeIntLittle(u32, spec.magic_number);
+    try writer.writeIntLittle(u32, (spec.version.major << 16) | (spec.version.minor) << 8);
+    try writer.writeIntLittle(u32, 0); // TODO: Register Zig compiler magic number.
+    try writer.writeIntLittle(u32, self.spirv_module.idBound());
+    try writer.writeIntLittle(u32, 0); // Schema.
+
+    // Declarations
+    for (module.decl_table.items()) |entry| {
+        const decl = entry.value;
+        switch (decl.typed_value) {
+            .most_recent => |tvm| {
+                const fn_data = &decl.fn_link.spirv;
+                for (fn_data.code.items) |word| {
+                    try writer.writeIntLittle(u32, word);
+                }
+            },
+            .never_succeeded => continue,
+        }
+    }
+
+    try bw.flush();
+}
src/link.zig
@@ -142,7 +142,7 @@ pub const File = struct {
         macho: MachO.SrcFn,
         c: C.FnBlock,
         wasm: ?Wasm.FnData,
-        spirv: void,
+        spirv: SpirV.FnData,
     };
 
     pub const Export = union {
@@ -180,7 +180,7 @@ pub const File = struct {
                 .macho => &(try MachO.createEmpty(allocator, options)).base,
                 .wasm => &(try Wasm.createEmpty(allocator, options)).base,
                 .c => unreachable, // Reported error earlier.
-                .spirv => return error.SpirVObjectFormatUnimplemented,
+                .spirv => &(try SpirV.createEmpty(allocator, options)).base,
                 .hex => return error.HexObjectFormatUnimplemented,
                 .raw => return error.RawObjectFormatUnimplemented,
             };
@@ -196,7 +196,7 @@ pub const File = struct {
                     .macho => &(try MachO.createEmpty(allocator, options)).base,
                     .wasm => &(try Wasm.createEmpty(allocator, options)).base,
                     .c => unreachable, // Reported error earlier.
-                    .spirv => return error.SpirVObjectFormatUnimplemented,
+                    .spirv => &(try SpirV.createEmpty(allocator, options)).base,
                     .hex => return error.HexObjectFormatUnimplemented,
                     .raw => return error.RawObjectFormatUnimplemented,
                 };
@@ -212,7 +212,7 @@ pub const File = struct {
             .macho => &(try MachO.openPath(allocator, sub_path, options)).base,
             .wasm => &(try Wasm.openPath(allocator, sub_path, options)).base,
             .c => &(try C.openPath(allocator, sub_path, options)).base,
-            .spirv => return error.SpirVObjectFormatUnimplemented,
+            .spirv => &(try SpirV.openPath(allocator, sub_path, options)).base,
             .hex => return error.HexObjectFormatUnimplemented,
             .raw => return error.RawObjectFormatUnimplemented,
         };
@@ -242,7 +242,7 @@ pub const File = struct {
                     .mode = determineMode(base.options),
                 });
             },
-            .c, .wasm => {},
+            .c, .wasm, .spirv => {},
         }
     }
 
@@ -287,7 +287,7 @@ pub const File = struct {
                 f.close();
                 base.file = null;
             },
-            .c, .wasm => {},
+            .c, .wasm, .spirv => {},
         }
     }
 
@@ -300,6 +300,7 @@ pub const File = struct {
             .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
             .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
             .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
+            .spirv => return @fieldParentPtr(SpirV, "base", base).updateDecl(module, decl),
         }
     }
 
@@ -309,7 +310,7 @@ pub const File = struct {
             .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
             .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
             .c => return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl),
-            .wasm => {},
+            .wasm, .spirv => {},
         }
     }
 
@@ -321,7 +322,7 @@ pub const File = struct {
             .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
             .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
             .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),
-            .wasm => {},
+            .wasm, .spirv => {},
         }
     }
 
@@ -368,6 +369,11 @@ pub const File = struct {
                 parent.deinit();
                 base.allocator.destroy(parent);
             },
+            .spirv => {
+                const parent = @fieldParentPtr(SpirV, "base", base);
+                parent.deinit();
+                base.allocator.destroy(parent);
+            },
         }
     }
 
@@ -401,6 +407,7 @@ pub const File = struct {
             .macho => return @fieldParentPtr(MachO, "base", base).flush(comp),
             .c => return @fieldParentPtr(C, "base", base).flush(comp),
             .wasm => return @fieldParentPtr(Wasm, "base", base).flush(comp),
+            .spirv => return @fieldParentPtr(SpirV, "base", base).flush(comp),
         }
     }
 
@@ -413,6 +420,7 @@ pub const File = struct {
             .macho => return @fieldParentPtr(MachO, "base", base).flushModule(comp),
             .c => return @fieldParentPtr(C, "base", base).flushModule(comp),
             .wasm => return @fieldParentPtr(Wasm, "base", base).flushModule(comp),
+            .spirv => return @fieldParentPtr(SpirV, "base", base).flushModule(comp),
         }
     }
 
@@ -424,6 +432,7 @@ pub const File = struct {
             .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
             .c => @fieldParentPtr(C, "base", base).freeDecl(decl),
             .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl),
+            .spirv => @fieldParentPtr(SpirV, "base", base).freeDecl(decl),
         }
     }
 
@@ -433,7 +442,7 @@ pub const File = struct {
             .elf => return @fieldParentPtr(Elf, "base", base).error_flags,
             .macho => return @fieldParentPtr(MachO, "base", base).error_flags,
             .c => return .{ .no_entry_point_found = false },
-            .wasm => return ErrorFlags{},
+            .wasm, .spirv => return ErrorFlags{},
         }
     }
 
@@ -451,6 +460,7 @@ pub const File = struct {
             .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
             .c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl, exports),
             .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports),
+            .spirv => return @fieldParentPtr(SpirV, "base", base).updateDeclExports(module, decl, exports),
         }
     }
 
@@ -461,6 +471,7 @@ pub const File = struct {
             .macho => return @fieldParentPtr(MachO, "base", base).getDeclVAddr(decl),
             .c => unreachable,
             .wasm => unreachable,
+            .spirv => unreachable,
         }
     }
 
src/main.zig
@@ -302,6 +302,7 @@ const usage_build_generic =
     \\    pe                      Portable Executable (Windows)
     \\    coff                    Common Object File Format (Windows)
     \\    macho                   macOS relocatables
+    \\    spirv                   Standard, Portable Intermediate Representation V (SPIR-V)
     \\    hex    (planned)        Intel IHEX
     \\    raw    (planned)        Dump machine code directly
     \\  -dirafter [dir]           Add directory to AFTER include search path
@@ -1515,6 +1516,8 @@ fn buildOutputType(
             break :blk .hex;
         } else if (mem.eql(u8, ofmt, "raw")) {
             break :blk .raw;
+        } else if (mem.eql(u8, ofmt, "spirv")) {
+            break :blk .spirv;
         } else {
             fatal("unsupported object format: {s}", .{ofmt});
         }
src/Module.zig
@@ -1622,7 +1622,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void
                             // in `Decl` to notice that the line number did not change.
                             self.comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl });
                         },
-                        .c, .wasm => {},
+                        .c, .wasm, .spirv => {},
                     }
                 }
             } else {
@@ -1855,6 +1855,7 @@ fn allocateNewDecl(
             .macho => .{ .macho = link.File.MachO.TextBlock.empty },
             .c => .{ .c = link.File.C.DeclBlock.empty },
             .wasm => .{ .wasm = {} },
+            .spirv => .{ .spirv = {} },
         },
         .fn_link = switch (mod.comp.bin_file.tag) {
             .coff => .{ .coff = {} },
@@ -1862,6 +1863,7 @@ fn allocateNewDecl(
             .macho => .{ .macho = link.File.MachO.SrcFn.empty },
             .c => .{ .c = link.File.C.FnBlock.empty },
             .wasm => .{ .wasm = null },
+            .spirv => .{ .spirv = .{} },
         },
         .generation = 0,
         .is_pub = false,
@@ -1959,6 +1961,7 @@ pub fn analyzeExport(
             .macho => .{ .macho = link.File.MachO.Export{} },
             .c => .{ .c = {} },
             .wasm => .{ .wasm = {} },
+            .spirv => .{ .spirv = {} },
         },
         .owner_decl = owner_decl,
         .exported_decl = exported_decl,