aboutsummaryrefslogtreecommitdiff
path: root/src/codegen/spirv
diff options
context:
space:
mode:
authorRobin Voetter <robin@voetter.nl>2024-11-09 21:50:33 +0100
committerGitHub <noreply@github.com>2024-11-09 21:50:33 +0100
commit62f4a6b4d877ce03d1e6f59cf794b5ebc6ea41d0 (patch)
treef86e516ad8fc2b14b43c57a48ba3a3db3a10e41a /src/codegen/spirv
parent35201e9d9338537a92de2ff89ea23dcd22ce4e52 (diff)
parent9cd7b8359c435a7a0c1309cbf529a100d5422b4f (diff)
downloadzig-62f4a6b4d877ce03d1e6f59cf794b5ebc6ea41d0.tar.gz
zig-62f4a6b4d877ce03d1e6f59cf794b5ebc6ea41d0.zip
Merge pull request #21937 from Snektron/spirv-vulkan-ptrs
spirv: miscellaneous vulkan + zig stuff
Diffstat (limited to 'src/codegen/spirv')
-rw-r--r--src/codegen/spirv/Assembler.zig105
1 files changed, 91 insertions, 14 deletions
diff --git a/src/codegen/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig
index 9e39f2ed09..2cfb590273 100644
--- a/src/codegen/spirv/Assembler.zig
+++ b/src/codegen/spirv/Assembler.zig
@@ -45,6 +45,9 @@ const Token = struct {
pipe,
/// =.
equals,
+ /// $identifier. This is used (for now) for constant values, like integers.
+ /// These can be used in place of a normal `value`.
+ placeholder,
fn name(self: Tag) []const u8 {
return switch (self) {
@@ -56,6 +59,7 @@ const Token = struct {
.string => "<string literal>",
.pipe => "'|'",
.equals => "'='",
+ .placeholder => "<placeholder>",
};
}
};
@@ -128,12 +132,19 @@ const AsmValue = union(enum) {
/// This result-value represents a type registered into the module's type system.
ty: IdRef,
+ /// This is a pre-supplied constant integer value.
+ constant: u32,
+
/// Retrieve the result-id of this AsmValue. Asserts that this AsmValue
/// is of a variant that allows the result to be obtained (not an unresolved
/// forward declaration, not in the process of being declared, etc).
pub fn resultId(self: AsmValue) IdRef {
return switch (self) {
- .just_declared, .unresolved_forward_reference => unreachable,
+ .just_declared,
+ .unresolved_forward_reference,
+ // TODO: Lower this value as constant?
+ .constant,
+ => unreachable,
.value => |result| result,
.ty => |result| result,
};
@@ -151,7 +162,8 @@ gpa: Allocator,
errors: std.ArrayListUnmanaged(ErrorMsg) = .empty,
/// The source code that is being assembled.
-src: []const u8,
+/// This is set when calling `assemble()`.
+src: []const u8 = undefined,
/// The module that this assembly is associated to.
/// Instructions like OpType*, OpDecorate, etc are emitted into this module.
@@ -211,7 +223,10 @@ pub fn deinit(self: *Assembler) void {
self.instruction_map.deinit(self.gpa);
}
-pub fn assemble(self: *Assembler) Error!void {
+pub fn assemble(self: *Assembler, src: []const u8) Error!void {
+ self.src = src;
+ self.errors.clearRetainingCapacity();
+
// Populate the opcode map if it isn't already
if (self.instruction_map.count() == 0) {
const instructions = spec.InstructionSet.core.instructions();
@@ -369,6 +384,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
/// - Function-local instructions are emitted in `self.func`.
fn processGenericInstruction(self: *Assembler) !?AsmValue {
const operands = self.inst.operands.items;
+ var maybe_spv_decl_index: ?SpvModule.Decl.Index = null;
const section = switch (self.inst.opcode.class()) {
.ConstantCreation => &self.spv.sections.types_globals_constants,
.Annotation => &self.spv.sections.annotations,
@@ -378,13 +394,16 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {
.OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes,
.OpVariable => switch (@as(spec.StorageClass, @enumFromInt(operands[2].value))) {
.Function => &self.func.prologue,
- .UniformConstant => &self.spv.sections.types_globals_constants,
- else => {
- // This is currently disabled because global variables are required to be
- // emitted in the proper order, and this should be honored in inline assembly
- // as well.
- return self.todo("global variables", .{});
+ .Input, .Output => section: {
+ maybe_spv_decl_index = try self.spv.allocDecl(.global);
+ try self.func.decl_deps.put(self.spv.gpa, maybe_spv_decl_index.?, {});
+ // TODO: In theory this can be non-empty if there is an initializer which depends on another global...
+ try self.spv.declareDeclDeps(maybe_spv_decl_index.?, &.{});
+ break :section &self.spv.sections.types_globals_constants;
},
+ // These don't need to be marked in the dependency system.
+ // Probably we should add them anyway, then filter out PushConstant globals.
+ else => &self.spv.sections.types_globals_constants,
},
// Default case - to be worked out further.
else => &self.func.body,
@@ -409,7 +428,10 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {
section.writeDoubleWord(dword);
},
.result_id => {
- maybe_result_id = self.spv.allocId();
+ maybe_result_id = if (maybe_spv_decl_index) |spv_decl_index|
+ self.spv.declPtr(spv_decl_index).result_id
+ else
+ self.spv.allocId();
try section.ensureUnusedCapacity(self.spv.gpa, 1);
section.writeOperand(IdResult, maybe_result_id.?);
},
@@ -475,8 +497,8 @@ fn resolveRefId(self: *Assembler, ref: AsmValue.Ref) !IdRef {
/// error message has been emitted into `self.errors`.
fn parseInstruction(self: *Assembler) !void {
self.inst.opcode = undefined;
- self.inst.operands.shrinkRetainingCapacity(0);
- self.inst.string_bytes.shrinkRetainingCapacity(0);
+ self.inst.operands.clearRetainingCapacity();
+ self.inst.string_bytes.clearRetainingCapacity();
const lhs_result_tok = self.currentToken();
const maybe_lhs_result: ?AsmValue.Ref = if (self.eatToken(.result_id_assign)) blk: {
@@ -654,6 +676,22 @@ fn parseRefId(self: *Assembler) !void {
fn parseLiteralInteger(self: *Assembler) !void {
const tok = self.currentToken();
+ if (self.eatToken(.placeholder)) {
+ const name = self.tokenText(tok)[1..];
+ const value = self.value_map.get(name) orelse {
+ return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
+ };
+ switch (value) {
+ .constant => |literal32| {
+ try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
+ },
+ else => {
+ return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
+ },
+ }
+ return;
+ }
+
try self.expectToken(.value);
// According to the SPIR-V machine readable grammar, a LiteralInteger
// may consist of one or more words. From the SPIR-V docs it seems like there
@@ -669,6 +707,22 @@ fn parseLiteralInteger(self: *Assembler) !void {
fn parseLiteralExtInstInteger(self: *Assembler) !void {
const tok = self.currentToken();
+ if (self.eatToken(.placeholder)) {
+ const name = self.tokenText(tok)[1..];
+ const value = self.value_map.get(name) orelse {
+ return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
+ };
+ switch (value) {
+ .constant => |literal32| {
+ try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
+ },
+ else => {
+ return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
+ },
+ }
+ return;
+ }
+
try self.expectToken(.value);
const text = self.tokenText(tok);
const value = std.fmt.parseInt(u32, text, 0) catch {
@@ -745,6 +799,22 @@ fn parseContextDependentNumber(self: *Assembler) !void {
fn parseContextDependentInt(self: *Assembler, signedness: std.builtin.Signedness, width: u32) !void {
const tok = self.currentToken();
+ if (self.eatToken(.placeholder)) {
+ const name = self.tokenText(tok)[1..];
+ const value = self.value_map.get(name) orelse {
+ return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
+ };
+ switch (value) {
+ .constant => |literal32| {
+ try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
+ },
+ else => {
+ return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
+ },
+ }
+ return;
+ }
+
try self.expectToken(.value);
if (width == 0 or width > 2 * @bitSizeOf(spec.Word)) {
@@ -848,6 +918,8 @@ fn tokenText(self: Assembler, tok: Token) []const u8 {
/// Tokenize `self.src` and put the tokens in `self.tokens`.
/// Any errors encountered are appended to `self.errors`.
fn tokenize(self: *Assembler) !void {
+ self.tokens.clearRetainingCapacity();
+
var offset: u32 = 0;
while (true) {
const tok = try self.nextToken(offset);
@@ -890,6 +962,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
string,
string_end,
escape,
+ placeholder,
} = .start;
var token_start = start_offset;
var offset = start_offset;
@@ -917,6 +990,10 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
offset += 1;
break;
},
+ '$' => {
+ state = .placeholder;
+ tag = .placeholder;
+ },
else => {
state = .value;
tag = .value;
@@ -932,11 +1009,11 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
' ', '\t', '\r', '\n', '=', '|' => break,
else => {},
},
- .result_id => switch (c) {
+ .result_id, .placeholder => switch (c) {
'_', 'a'...'z', 'A'...'Z', '0'...'9' => {},
' ', '\t', '\r', '\n', '=', '|' => break,
else => {
- try self.addError(offset, "illegal character in result-id", .{});
+ try self.addError(offset, "illegal character in result-id or placeholder", .{});
// Again, probably a forgotten delimiter here.
break;
},