mirror of
https://github.com/ziglang/zig.git
synced 2025-12-24 07:03:11 +00:00
SPIR-V: Begin generating types
This commit is contained in:
parent
fa3afede58
commit
d45e7dfc24
@ -1,9 +1,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const log = std.log.scoped(.codegen);
|
||||||
|
|
||||||
const spec = @import("spirv/spec.zig");
|
const spec = @import("spirv/spec.zig");
|
||||||
const Module = @import("../Module.zig");
|
const Module = @import("../Module.zig");
|
||||||
const Decl = Module.Decl;
|
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 {
|
pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void {
|
||||||
const word_count = @intCast(u32, args.len + 1);
|
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 {
|
pub const SPIRVModule = struct {
|
||||||
next_id: u32 = 0,
|
next_result_id: u32 = 0,
|
||||||
free_id_list: std.ArrayList(u32),
|
|
||||||
|
|
||||||
pub fn init(allocator: *Allocator) SPIRVModule {
|
target: std.Target,
|
||||||
|
|
||||||
|
types: TypeMap,
|
||||||
|
|
||||||
|
types_and_globals: std.ArrayList(u32),
|
||||||
|
fn_decls: std.ArrayList(u32),
|
||||||
|
|
||||||
|
pub fn init(target: std.Target, allocator: *Allocator) SPIRVModule {
|
||||||
return .{
|
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 {
|
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 {
|
pub fn allocResultId(self: *SPIRVModule) u32 {
|
||||||
if (self.free_id_list.popOrNull()) |id| return id;
|
defer self.next_result_id += 1;
|
||||||
|
return self.next_result_id;
|
||||||
defer self.next_id += 1;
|
|
||||||
return self.next_id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn freeId(self: *SPIRVModule, id: u32) void {
|
pub fn resultIdBound(self: *SPIRVModule) u32 {
|
||||||
if (id + 1 == self.next_id) {
|
return self.next_result_id;
|
||||||
self.next_id -= 1;
|
}
|
||||||
} else {
|
|
||||||
// If no more memory to append the id to the free list, just ignore it.
|
pub fn getOrGenType(self: *SPIRVModule, t: Type) !u32 {
|
||||||
self.free_id_list.append(id) catch {};
|
// We can't use getOrPut here so we can recursively generate types.
|
||||||
|
if (self.types.get(t)) |already_generated| {
|
||||||
|
return already_generated;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn idBound(self: *SPIRVModule) u32 {
|
|
||||||
return self.next_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn genDecl(self: SPIRVModule, id: u32, code: *std.ArrayList(u32), decl: *Decl) !void {}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,11 +16,16 @@
|
|||||||
//! All function declarations without a body (extern functions presumably).
|
//! All function declarations without a body (extern functions presumably).
|
||||||
//! All regular functions.
|
//! 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 SpirV = @This();
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const log = std.log.scoped(.link);
|
||||||
|
|
||||||
const Module = @import("../Module.zig");
|
const Module = @import("../Module.zig");
|
||||||
const Compilation = @import("../Compilation.zig");
|
const Compilation = @import("../Compilation.zig");
|
||||||
@ -30,16 +35,15 @@ const trace = @import("../tracy.zig").trace;
|
|||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
const spec = @import("../codegen/spirv/spec.zig");
|
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 {
|
pub const FnData = struct {
|
||||||
id: ?u32 = null,
|
// We're going to fill these in flushModule, and we're going to fill them unconditionally,
|
||||||
code: std.ArrayListUnmanaged(u32) = .{},
|
// so just set it to undefined.
|
||||||
|
id: u32 = undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
base: link.File,
|
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 {
|
pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
|
||||||
const spirv = try gpa.create(SpirV);
|
const spirv = try gpa.create(SpirV);
|
||||||
spirv.* = .{
|
spirv.* = .{
|
||||||
@ -49,7 +53,6 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
|
|||||||
.file = null,
|
.file = null,
|
||||||
.allocator = gpa,
|
.allocator = gpa,
|
||||||
},
|
},
|
||||||
.spirv_module = codegen.SPIRVModule.init(gpa),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Figure out where to put all of these
|
// 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;
|
return spirv;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *SpirV) void {
|
pub fn deinit(self: *SpirV) void {}
|
||||||
self.spirv_module.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !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(
|
pub fn updateDeclExports(
|
||||||
self: *SpirV,
|
self: *SpirV,
|
||||||
@ -117,12 +101,7 @@ pub fn updateDeclExports(
|
|||||||
exports: []const *Module.Export,
|
exports: []const *Module.Export,
|
||||||
) !void {}
|
) !void {}
|
||||||
|
|
||||||
pub fn freeDecl(self: *SpirV, decl: *Module.Decl) 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 flush(self: *SpirV, comp: *Compilation) !void {
|
pub fn flush(self: *SpirV, comp: *Compilation) !void {
|
||||||
if (build_options.have_llvm and self.base.options.use_lld) {
|
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 module = self.base.options.module.?;
|
||||||
const target = comp.getTarget();
|
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);
|
var binary = std.ArrayList(u32).init(self.base.allocator);
|
||||||
defer binary.deinit();
|
defer binary.deinit();
|
||||||
|
|
||||||
// Note: The order of adding sections to the final binary
|
|
||||||
// follows the SPIR-V logical module format!
|
|
||||||
|
|
||||||
try binary.appendSlice(&[_]u32{
|
try binary.appendSlice(&[_]u32{
|
||||||
spec.magic_number,
|
spec.magic_number,
|
||||||
(spec.version.major << 16) | (spec.version.minor << 8),
|
(spec.version.major << 16) | (spec.version.minor << 8),
|
||||||
0, // TODO: Register Zig compiler magic number.
|
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).
|
0, // Schema (currently reserved for future use in the SPIR-V spec).
|
||||||
});
|
});
|
||||||
|
|
||||||
try writeCapabilities(&binary, target);
|
try writeCapabilities(&binary, target);
|
||||||
try writeMemoryModel(&binary, target);
|
try writeMemoryModel(&binary, target);
|
||||||
|
|
||||||
// Collect list of buffers to write.
|
// Note: The order of adding sections to the final binary
|
||||||
// SPIR-V files support both little and big endian words. The actual format is
|
// follows the SPIR-V logical module format!
|
||||||
// disambiguated by the magic number, and so theoretically we don't need to worry
|
var all_buffers = [_]std.os.iovec_const{
|
||||||
// about endian-ness when writing the final binary.
|
wordsToIovConst(binary.items),
|
||||||
var all_buffers = std.ArrayList(std.os.iovec_const).init(self.base.allocator);
|
wordsToIovConst(spirv_module.types_and_globals.items),
|
||||||
defer all_buffers.deinit();
|
wordsToIovConst(spirv_module.fn_decls.items),
|
||||||
|
};
|
||||||
|
|
||||||
// Pre-allocate enough for the binary info + all functions
|
const file = self.base.file.?;
|
||||||
try all_buffers.ensureCapacity(module.decl_table.count() + 1);
|
const bytes = std.mem.sliceAsBytes(binary.items);
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_size: u64 = 0;
|
var file_size: u64 = 0;
|
||||||
for (all_buffers.items) |iov| {
|
for (all_buffers) |iov| {
|
||||||
file_size += iov.iov_len;
|
file_size += iov.iov_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = self.base.file.?;
|
|
||||||
try file.seekTo(0);
|
try file.seekTo(0);
|
||||||
try file.setEndPos(file_size);
|
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 {
|
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_base = bytes.ptr,
|
||||||
.iov_len = bytes.len,
|
.iov_len = bytes.len,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -118,11 +118,16 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render(writer: Writer, registry: Registry) !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) {
|
switch (registry) {
|
||||||
.core => |core_reg| {
|
.core => |core_reg| {
|
||||||
try renderCopyRight(writer, core_reg.copyright);
|
|
||||||
try writer.print(
|
try writer.print(
|
||||||
\\const Version = @import("builtin").Version;
|
|
||||||
\\pub const version = Version{{.major = {}, .minor = {}, .patch = {}}};
|
\\pub const version = Version{{.major = {}, .minor = {}, .patch = {}}};
|
||||||
\\pub const magic_number: u32 = {s};
|
\\pub const magic_number: u32 = {s};
|
||||||
\\
|
\\
|
||||||
@ -132,9 +137,7 @@ fn render(writer: Writer, registry: Registry) !void {
|
|||||||
try renderOperandKinds(writer, core_reg.operand_kinds);
|
try renderOperandKinds(writer, core_reg.operand_kinds);
|
||||||
},
|
},
|
||||||
.extension => |ext_reg| {
|
.extension => |ext_reg| {
|
||||||
try renderCopyRight(writer, ext_reg.copyright);
|
|
||||||
try writer.print(
|
try writer.print(
|
||||||
\\const Version = @import("builtin").Version;
|
|
||||||
\\pub const version = Version{{.major = {}, .minor = 0, .patch = {}}};
|
\\pub const version = Version{{.major = {}, .minor = 0, .patch = {}}};
|
||||||
\\
|
\\
|
||||||
, .{ ext_reg.version, ext_reg.revision },
|
, .{ 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 {
|
fn renderOpcodes(writer: Writer, instructions: []const Instruction) !void {
|
||||||
try writer.writeAll("pub const Opcode = extern enum(u16) {\n");
|
try writer.writeAll("pub const Opcode = extern enum(u16) {\n");
|
||||||
for (instructions) |instr| {
|
for (instructions) |instr| {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user