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(
1760517605 } else break :rs ptr_src;
1760617606 };
1760717607
17608 try sema.requireRuntimeBlock(block, op_src, runtime_src);
17609
1760817610 const target = zcu.getTarget();
1760917611 if (target_util.arePointersLogical(target, ptr_info.flags.address_space)) {
1761017612 return sema.failWithOwnedErrorMsg(block, msg: {
......@@ -17623,7 +17625,6 @@ fn analyzePtrArithmetic(
1762317625 });
1762417626 }
1762517627
17626 try sema.requireRuntimeBlock(block, op_src, runtime_src);
1762717628 return block.addInst(.{
1762817629 .tag = air_tag,
1762917630 .data = .{ .ty_pl = .{
src/codegen/spirv.zig+53-6
......@@ -6556,13 +6556,59 @@ const NavGen = struct {
65566556 // for the string, we still use the next u32 for the null terminator.
65576557 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
65586558
6559 if (self.typeOf(input).zigTypeTag(zcu) == .type) {
6560 // This assembly input is a type instead of a value.
6561 // That's fine for now, just make sure to resolve it as such.
6562 const val = (try self.air.value(input, self.pt)).?;
6563 const ty_id = try self.resolveType(val.toType(), .direct);
6564 try as.value_map.put(as.gpa, name, .{ .ty = ty_id });
6559 const input_ty = self.typeOf(input);
6560
6561 if (std.mem.eql(u8, constraint, "c")) {
6562 // constant
6563 const val = (try self.air.value(input, self.pt)) orelse {
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 }
65656607 } 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
65666612 const val_id = try self.resolve(input);
65676613 try as.value_map.put(as.gpa, name, .{ .value = val_id });
65686614 }
......@@ -6624,6 +6670,7 @@ const NavGen = struct {
66246670 .just_declared, .unresolved_forward_reference => unreachable,
66256671 .ty => return self.fail("cannot return spir-v type as value from assembly", .{}),
66266672 .value => |ref| return ref,
6673 .constant => return self.fail("cannot return constant from assembly", .{}),
66276674 }
66286675
66296676 // TODO: Multiple results
src/codegen/spirv/Assembler.zig+71-7
......@@ -45,6 +45,9 @@ const Token = struct {
4545 pipe,
4646 /// =.
4747 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
4952 fn name(self: Tag) []const u8 {
5053 return switch (self) {
......@@ -56,6 +59,7 @@ const Token = struct {
5659 .string => "<string literal>",
5760 .pipe => "'|'",
5861 .equals => "'='",
62 .placeholder => "<placeholder>",
5963 };
6064 }
6165 };
......@@ -128,12 +132,19 @@ const AsmValue = union(enum) {
128132 /// This result-value represents a type registered into the module's type system.
129133 ty: IdRef,
130134
135 /// This is a pre-supplied constant integer value.
136 constant: u32,
137
131138 /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue
132139 /// is of a variant that allows the result to be obtained (not an unresolved
133140 /// forward declaration, not in the process of being declared, etc).
134141 pub fn resultId(self: AsmValue) IdRef {
135142 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,
137148 .value => |result| result,
138149 .ty => |result| result,
139150 };
......@@ -383,16 +394,16 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {
383394 .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes,
384395 .OpVariable => switch (@as(spec.StorageClass, @enumFromInt(operands[2].value))) {
385396 .Function => &self.func.prologue,
386 // These don't need to be marked in the dependency system.
387 // Probably we should add them anyway, then filter out PushConstant globals.
388 .PushConstant => &self.spv.sections.types_globals_constants,
389 else => section: {
397 .Input, .Output => section: {
390398 maybe_spv_decl_index = try self.spv.allocDecl(.global);
391399 try self.func.decl_deps.put(self.spv.gpa, maybe_spv_decl_index.?, {});
392400 // TODO: In theory this can be non-empty if there is an initializer which depends on another global...
393401 try self.spv.declareDeclDeps(maybe_spv_decl_index.?, &.{});
394402 break :section &self.spv.sections.types_globals_constants;
395403 },
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,
396407 },
397408 // Default case - to be worked out further.
398409 else => &self.func.body,
......@@ -665,6 +676,22 @@ fn parseRefId(self: *Assembler) !void {
665676
666677fn parseLiteralInteger(self: *Assembler) !void {
667678 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
668695 try self.expectToken(.value);
669696 // According to the SPIR-V machine readable grammar, a LiteralInteger
670697 // 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 {
680707
681708fn parseLiteralExtInstInteger(self: *Assembler) !void {
682709 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
683726 try self.expectToken(.value);
684727 const text = self.tokenText(tok);
685728 const value = std.fmt.parseInt(u32, text, 0) catch {
......@@ -756,6 +799,22 @@ fn parseContextDependentNumber(self: *Assembler) !void {
756799
757800fn parseContextDependentInt(self: *Assembler, signedness: std.builtin.Signedness, width: u32) !void {
758801 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
759818 try self.expectToken(.value);
760819
761820 if (width == 0 or width > 2 * @bitSizeOf(spec.Word)) {
......@@ -903,6 +962,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
903962 string,
904963 string_end,
905964 escape,
965 placeholder,
906966 } = .start;
907967 var token_start = start_offset;
908968 var offset = start_offset;
......@@ -930,6 +990,10 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
930990 offset += 1;
931991 break;
932992 },
993 '$' => {
994 state = .placeholder;
995 tag = .placeholder;
996 },
933997 else => {
934998 state = .value;
935999 tag = .value;
......@@ -945,11 +1009,11 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
9451009 ' ', '\t', '\r', '\n', '=', '|' => break,
9461010 else => {},
9471011 },
948 .result_id => switch (c) {
1012 .result_id, .placeholder => switch (c) {
9491013 '_', 'a'...'z', 'A'...'Z', '0'...'9' => {},
9501014 ' ', '\t', '\r', '\n', '=', '|' => break,
9511015 else => {
952 try self.addError(offset, "illegal character in result-id", .{});
1016 try self.addError(offset, "illegal character in result-id or placeholder", .{});
9531017 // Again, probably a forgotten delimiter here.
9541018 break;
9551019 },