authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-11-02 16:05:06+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-11-08 20:43:55+01:00
log688d7055e3c483249391c66bfe02c084477ad81b
treea66ba96967aa74121a13c30e4d7650f1bcd8d1f2
parentb5301558aecdb1939c203681bf67224260ac6427
signaturebadge-check Signed by SSH key SHA256:ZS52FNyUv2WUXvO4njmVaFVO46RHojFuOrxRc4LuKzg

spirv: assembler hacky constant placeholders


3 files changed, 126 insertions(+), 14 deletions(-)

src/Sema.zig+2-1
...@@ -17605,6 +17605,8 @@ fn analyzePtrArithmetic(...@@ -17605,6 +17605,8 @@ fn analyzePtrArithmetic(
17605 } else break :rs ptr_src;17605 } else break :rs ptr_src;
17606 };17606 };
1760717607
17608 try sema.requireRuntimeBlock(block, op_src, runtime_src);
17609
17608 const target = zcu.getTarget();17610 const target = zcu.getTarget();
17609 if (target_util.arePointersLogical(target, ptr_info.flags.address_space)) {17611 if (target_util.arePointersLogical(target, ptr_info.flags.address_space)) {
17610 return sema.failWithOwnedErrorMsg(block, msg: {17612 return sema.failWithOwnedErrorMsg(block, msg: {
...@@ -17623,7 +17625,6 @@ fn analyzePtrArithmetic(...@@ -17623,7 +17625,6 @@ fn analyzePtrArithmetic(
17623 });17625 });
17624 }17626 }
1762517627
17626 try sema.requireRuntimeBlock(block, op_src, runtime_src);
17627 return block.addInst(.{17628 return block.addInst(.{
17628 .tag = air_tag,17629 .tag = air_tag,
17629 .data = .{ .ty_pl = .{17630 .data = .{ .ty_pl = .{
src/codegen/spirv.zig+53-6
...@@ -6556,13 +6556,59 @@ const NavGen = struct {...@@ -6556,13 +6556,59 @@ const NavGen = struct {
6556 // for the string, we still use the next u32 for the null terminator.6556 // for the string, we still use the next u32 for the null terminator.
6557 extra_i += (constraint.len + name.len + (2 + 3)) / 4;6557 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
65586558
6559 if (self.typeOf(input).zigTypeTag(zcu) == .type) {6559 const input_ty = self.typeOf(input);
6560 // This assembly input is a type instead of a value.6560
6561 // That's fine for now, just make sure to resolve it as such.6561 if (std.mem.eql(u8, constraint, "c")) {
6562 const val = (try self.air.value(input, self.pt)).?;6562 // constant
6563 const ty_id = try self.resolveType(val.toType(), .direct);6563 const val = (try self.air.value(input, self.pt)) orelse {
6564 try as.value_map.put(as.gpa, name, .{ .ty = ty_id });6564 return self.fail("assembly inputs with 'c' constraint have to be compile-time known", .{});
6565 };
6566
6567 // TODO: This entire function should be handled a bit better...
6568 const ip = &zcu.intern_pool;
6569 switch (ip.indexToKey(val.toIntern())) {
6570 .int_type,
6571 .ptr_type,
6572 .array_type,
6573 .vector_type,
6574 .opt_type,
6575 .anyframe_type,
6576 .error_union_type,
6577 .simple_type,
6578 .struct_type,
6579 .union_type,
6580 .opaque_type,
6581 .enum_type,
6582 .func_type,
6583 .error_set_type,
6584 .inferred_error_set_type,
6585 => unreachable, // types, not values
6586
6587 .undef => return self.fail("assembly input with 'c' constraint cannot be undefined", .{}),
6588
6589 .int => {
6590 try as.value_map.put(as.gpa, name, .{ .constant = @intCast(val.toUnsignedInt(zcu)) });
6591 },
6592
6593 else => unreachable, // TODO
6594 }
6595 } else if (std.mem.eql(u8, constraint, "t")) {
6596 // type
6597 if (input_ty.zigTypeTag(zcu) == .type) {
6598 // This assembly input is a type instead of a value.
6599 // That's fine for now, just make sure to resolve it as such.
6600 const val = (try self.air.value(input, self.pt)).?;
6601 const ty_id = try self.resolveType(val.toType(), .direct);
6602 try as.value_map.put(as.gpa, name, .{ .ty = ty_id });
6603 } else {
6604 const ty_id = try self.resolveType(input_ty, .direct);
6605 try as.value_map.put(as.gpa, name, .{ .ty = ty_id });
6606 }
6565 } else {6607 } else {
6608 if (input_ty.zigTypeTag(zcu) == .type) {
6609 return self.fail("use the 't' constraint to supply types to SPIR-V inline assembly", .{});
6610 }
6611
6566 const val_id = try self.resolve(input);6612 const val_id = try self.resolve(input);
6567 try as.value_map.put(as.gpa, name, .{ .value = val_id });6613 try as.value_map.put(as.gpa, name, .{ .value = val_id });
6568 }6614 }
...@@ -6624,6 +6670,7 @@ const NavGen = struct {...@@ -6624,6 +6670,7 @@ const NavGen = struct {
6624 .just_declared, .unresolved_forward_reference => unreachable,6670 .just_declared, .unresolved_forward_reference => unreachable,
6625 .ty => return self.fail("cannot return spir-v type as value from assembly", .{}),6671 .ty => return self.fail("cannot return spir-v type as value from assembly", .{}),
6626 .value => |ref| return ref,6672 .value => |ref| return ref,
6673 .constant => return self.fail("cannot return constant from assembly", .{}),
6627 }6674 }
66286675
6629 // TODO: Multiple results6676 // TODO: Multiple results
src/codegen/spirv/Assembler.zig+71-7
...@@ -45,6 +45,9 @@ const Token = struct {...@@ -45,6 +45,9 @@ const Token = struct {
45 pipe,45 pipe,
46 /// =.46 /// =.
47 equals,47 equals,
48 /// $identifier. This is used (for now) for constant values, like integers.
49 /// These can be used in place of a normal `value`.
50 placeholder,
4851
49 fn name(self: Tag) []const u8 {52 fn name(self: Tag) []const u8 {
50 return switch (self) {53 return switch (self) {
...@@ -56,6 +59,7 @@ const Token = struct {...@@ -56,6 +59,7 @@ const Token = struct {
56 .string => "<string literal>",59 .string => "<string literal>",
57 .pipe => "'|'",60 .pipe => "'|'",
58 .equals => "'='",61 .equals => "'='",
62 .placeholder => "<placeholder>",
59 };63 };
60 }64 }
61 };65 };
...@@ -128,12 +132,19 @@ const AsmValue = union(enum) {...@@ -128,12 +132,19 @@ const AsmValue = union(enum) {
128 /// This result-value represents a type registered into the module's type system.132 /// This result-value represents a type registered into the module's type system.
129 ty: IdRef,133 ty: IdRef,
130134
135 /// This is a pre-supplied constant integer value.
136 constant: u32,
137
131 /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue138 /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue
132 /// is of a variant that allows the result to be obtained (not an unresolved139 /// is of a variant that allows the result to be obtained (not an unresolved
133 /// forward declaration, not in the process of being declared, etc).140 /// forward declaration, not in the process of being declared, etc).
134 pub fn resultId(self: AsmValue) IdRef {141 pub fn resultId(self: AsmValue) IdRef {
135 return switch (self) {142 return switch (self) {
136 .just_declared, .unresolved_forward_reference => unreachable,143 .just_declared,
144 .unresolved_forward_reference,
145 // TODO: Lower this value as constant?
146 .constant,
147 => unreachable,
137 .value => |result| result,148 .value => |result| result,
138 .ty => |result| result,149 .ty => |result| result,
139 };150 };
...@@ -383,16 +394,16 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {...@@ -383,16 +394,16 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {
383 .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes,394 .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes,
384 .OpVariable => switch (@as(spec.StorageClass, @enumFromInt(operands[2].value))) {395 .OpVariable => switch (@as(spec.StorageClass, @enumFromInt(operands[2].value))) {
385 .Function => &self.func.prologue,396 .Function => &self.func.prologue,
386 // These don't need to be marked in the dependency system.397 .Input, .Output => section: {
387 // Probably we should add them anyway, then filter out PushConstant globals.
388 .PushConstant => &self.spv.sections.types_globals_constants,
389 else => section: {
390 maybe_spv_decl_index = try self.spv.allocDecl(.global);398 maybe_spv_decl_index = try self.spv.allocDecl(.global);
391 try self.func.decl_deps.put(self.spv.gpa, maybe_spv_decl_index.?, {});399 try self.func.decl_deps.put(self.spv.gpa, maybe_spv_decl_index.?, {});
392 // TODO: In theory this can be non-empty if there is an initializer which depends on another global...400 // TODO: In theory this can be non-empty if there is an initializer which depends on another global...
393 try self.spv.declareDeclDeps(maybe_spv_decl_index.?, &.{});401 try self.spv.declareDeclDeps(maybe_spv_decl_index.?, &.{});
394 break :section &self.spv.sections.types_globals_constants;402 break :section &self.spv.sections.types_globals_constants;
395 },403 },
404 // These don't need to be marked in the dependency system.
405 // Probably we should add them anyway, then filter out PushConstant globals.
406 else => &self.spv.sections.types_globals_constants,
396 },407 },
397 // Default case - to be worked out further.408 // Default case - to be worked out further.
398 else => &self.func.body,409 else => &self.func.body,
...@@ -665,6 +676,22 @@ fn parseRefId(self: *Assembler) !void {...@@ -665,6 +676,22 @@ fn parseRefId(self: *Assembler) !void {
665676
666fn parseLiteralInteger(self: *Assembler) !void {677fn parseLiteralInteger(self: *Assembler) !void {
667 const tok = self.currentToken();678 const tok = self.currentToken();
679 if (self.eatToken(.placeholder)) {
680 const name = self.tokenText(tok)[1..];
681 const value = self.value_map.get(name) orelse {
682 return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
683 };
684 switch (value) {
685 .constant => |literal32| {
686 try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
687 },
688 else => {
689 return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
690 },
691 }
692 return;
693 }
694
668 try self.expectToken(.value);695 try self.expectToken(.value);
669 // According to the SPIR-V machine readable grammar, a LiteralInteger696 // According to the SPIR-V machine readable grammar, a LiteralInteger
670 // may consist of one or more words. From the SPIR-V docs it seems like there697 // may consist of one or more words. From the SPIR-V docs it seems like there
...@@ -680,6 +707,22 @@ fn parseLiteralInteger(self: *Assembler) !void {...@@ -680,6 +707,22 @@ fn parseLiteralInteger(self: *Assembler) !void {
680707
681fn parseLiteralExtInstInteger(self: *Assembler) !void {708fn parseLiteralExtInstInteger(self: *Assembler) !void {
682 const tok = self.currentToken();709 const tok = self.currentToken();
710 if (self.eatToken(.placeholder)) {
711 const name = self.tokenText(tok)[1..];
712 const value = self.value_map.get(name) orelse {
713 return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
714 };
715 switch (value) {
716 .constant => |literal32| {
717 try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
718 },
719 else => {
720 return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
721 },
722 }
723 return;
724 }
725
683 try self.expectToken(.value);726 try self.expectToken(.value);
684 const text = self.tokenText(tok);727 const text = self.tokenText(tok);
685 const value = std.fmt.parseInt(u32, text, 0) catch {728 const value = std.fmt.parseInt(u32, text, 0) catch {
...@@ -756,6 +799,22 @@ fn parseContextDependentNumber(self: *Assembler) !void {...@@ -756,6 +799,22 @@ fn parseContextDependentNumber(self: *Assembler) !void {
756799
757fn parseContextDependentInt(self: *Assembler, signedness: std.builtin.Signedness, width: u32) !void {800fn parseContextDependentInt(self: *Assembler, signedness: std.builtin.Signedness, width: u32) !void {
758 const tok = self.currentToken();801 const tok = self.currentToken();
802 if (self.eatToken(.placeholder)) {
803 const name = self.tokenText(tok)[1..];
804 const value = self.value_map.get(name) orelse {
805 return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
806 };
807 switch (value) {
808 .constant => |literal32| {
809 try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
810 },
811 else => {
812 return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
813 },
814 }
815 return;
816 }
817
759 try self.expectToken(.value);818 try self.expectToken(.value);
760819
761 if (width == 0 or width > 2 * @bitSizeOf(spec.Word)) {820 if (width == 0 or width > 2 * @bitSizeOf(spec.Word)) {
...@@ -903,6 +962,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {...@@ -903,6 +962,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
903 string,962 string,
904 string_end,963 string_end,
905 escape,964 escape,
965 placeholder,
906 } = .start;966 } = .start;
907 var token_start = start_offset;967 var token_start = start_offset;
908 var offset = start_offset;968 var offset = start_offset;
...@@ -930,6 +990,10 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {...@@ -930,6 +990,10 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
930 offset += 1;990 offset += 1;
931 break;991 break;
932 },992 },
993 '$' => {
994 state = .placeholder;
995 tag = .placeholder;
996 },
933 else => {997 else => {
934 state = .value;998 state = .value;
935 tag = .value;999 tag = .value;
...@@ -945,11 +1009,11 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {...@@ -945,11 +1009,11 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
945 ' ', '\t', '\r', '\n', '=', '|' => break,1009 ' ', '\t', '\r', '\n', '=', '|' => break,
946 else => {},1010 else => {},
947 },1011 },
948 .result_id => switch (c) {1012 .result_id, .placeholder => switch (c) {
949 '_', 'a'...'z', 'A'...'Z', '0'...'9' => {},1013 '_', 'a'...'z', 'A'...'Z', '0'...'9' => {},
950 ' ', '\t', '\r', '\n', '=', '|' => break,1014 ' ', '\t', '\r', '\n', '=', '|' => break,
951 else => {1015 else => {
952 try self.addError(offset, "illegal character in result-id", .{});1016 try self.addError(offset, "illegal character in result-id or placeholder", .{});
953 // Again, probably a forgotten delimiter here.1017 // Again, probably a forgotten delimiter here.
954 break;1018 break;
955 },1019 },