lib/choir/src/backends/artifact/model/linkage/signature.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 /// A scalar type that crosses a function boundary.
4 pub const ScalarType = enum(u8) {
5 i8 = 1,
6 i16 = 2,
7 i32 = 3,
8 i64 = 4,
9 u8 = 5,
10 u16 = 6,
11 u32 = 7,
12 u64 = 8,
13 index = 9,
14 bool = 10,
15 f32 = 11,
16 f64 = 12,
17 };
18
19 /// A fixed number of lanes of one scalar type.
20 pub const VectorType = struct {
21 element: ScalarType,
22 lanes: u8,
23
24 /// Most lanes one vector type holds.
25 pub const max_lanes = 16;
26 };
27
28 /// The kind of a `ValueType`.
29 pub const ValueKind = enum(u8) {
30 scalar = 1,
31 memref = 2,
32 vector = 3,
33 };
34
35 /// The type of one parameter or result of a machine-code function.
36 pub const ValueType = union(ValueKind) {
37 scalar: ScalarType,
38 /// An address passed as one 64-bit word, from a memref of any address space.
39 memref,
40 vector: VectorType,
41
42 /// Reports whether a vector type holds between 1 and `VectorType.max_lanes` lanes.
43 /// Scalar and memref types are always valid.
44 pub fn isValid(self: ValueType) bool {
45 return switch (self) {
46 .scalar, .memref => true,
47 .vector => |vector| vector.lanes != 0 and vector.lanes <= VectorType.max_lanes,
48 };
49 }
50
51 pub fn eql(self: ValueType, other: ValueType) bool {
52 return std.meta.eql(self, other);
53 }
54 };
55
56 pub const SignatureError = error{ TooManyParameters, TooManyResults, InvalidValueType };
57
58 /// The parameter and result types of one machine-code function.
59 pub const Signature = struct {
60 parameter_count: u8 = 0,
61 result_count: u8 = 0,
62 /// Entries past `parameter_count` hold `.memref`.
63 parameter_types: [max_parameters]ValueType = @splat(.memref),
64 /// Entries past `result_count` hold `.memref`.
65 result_types: [max_results]ValueType = @splat(.memref),
66
67 /// Most parameters one signature records.
68 pub const max_parameters = 64;
69 /// Most results one signature records.
70 pub const max_results = 16;
71
72 comptime {
73 std.debug.assert(max_parameters <= std.math.maxInt(u8));
74 std.debug.assert(max_results <= std.math.maxInt(u8));
75 std.debug.assert(VectorType.max_lanes <= std.math.maxInt(u8));
76 }
77
78 /// Records `parameter_types` and `result_types`. Fails with `error.TooManyParameters` or
79 /// `error.TooManyResults` when a list exceeds its maximum, and with `error.InvalidValueType`
80 /// when a vector type holds no lanes or more than `VectorType.max_lanes`.
81 pub fn init(
82 parameter_types: []const ValueType,
83 result_types: []const ValueType,
84 ) SignatureError!Signature {
85 if (parameter_types.len > max_parameters) return error.TooManyParameters;
86 if (result_types.len > max_results) return error.TooManyResults;
87 for (parameter_types) |value_type| {
88 if (!value_type.isValid()) return error.InvalidValueType;
89 }
90 for (result_types) |value_type| {
91 if (!value_type.isValid()) return error.InvalidValueType;
92 }
93 var signature = Signature{
94 .parameter_count = @intCast(parameter_types.len),
95 .result_count = @intCast(result_types.len),
96 };
97 @memcpy(signature.parameter_types[0..parameter_types.len], parameter_types);
98 @memcpy(signature.result_types[0..result_types.len], result_types);
99 std.debug.assert(signature.parameters().len == parameter_types.len);
100 std.debug.assert(signature.results().len == result_types.len);
101 std.debug.assert(signature.isValid());
102 return signature;
103 }
104
105 pub fn parameters(self: *const Signature) []const ValueType {
106 std.debug.assert(self.parameter_count <= max_parameters);
107 return self.parameter_types[0..self.parameter_count];
108 }
109
110 pub fn results(self: *const Signature) []const ValueType {
111 std.debug.assert(self.result_count <= max_results);
112 return self.result_types[0..self.result_count];
113 }
114
115 /// Reports whether both counts are within their maximums and every recorded type is valid.
116 /// Every signature from `init` is valid. Writes to the fields can break that.
117 pub fn isValid(self: *const Signature) bool {
118 if (self.parameter_count > max_parameters) return false;
119 if (self.result_count > max_results) return false;
120 for (self.parameter_types[0..self.parameter_count]) |value_type| {
121 if (!value_type.isValid()) return false;
122 }
123 for (self.result_types[0..self.result_count]) |value_type| {
124 if (!value_type.isValid()) return false;
125 }
126 return true;
127 }
128
129 /// Reports whether `self` and `other` record the same parameter and result types.
130 /// Entries past either count do not take part.
131 pub fn eql(self: *const Signature, other: *const Signature) bool {
132 return typesEql(self.parameters(), other.parameters()) and
133 typesEql(self.results(), other.results());
134 }
135 };
136
137 fn typesEql(left: []const ValueType, right: []const ValueType) bool {
138 if (left.len != right.len) return false;
139 for (left, right) |left_type, right_type| {
140 if (!left_type.eql(right_type)) return false;
141 }
142 return true;
143 }
144
145 test "signatures compare only the types they record" {
146 const int64 = ValueType{ .scalar = .i64 };
147 const float64 = ValueType{ .scalar = .f64 };
148 const pair = try Signature.init(&.{ int64, .memref }, &.{float64});
149 var same = try Signature.init(&.{ int64, .memref }, &.{float64});
150 try std.testing.expect(pair.eql(&same));
151 same.parameter_types[Signature.max_parameters - 1] = int64;
152 same.result_types[Signature.max_results - 1] = float64;
153 try std.testing.expect(pair.eql(&same));
154
155 const narrower = try Signature.init(&.{ .{ .scalar = .i32 }, .memref }, &.{float64});
156 try std.testing.expect(!pair.eql(&narrower));
157 const shorter = try Signature.init(&.{int64}, &.{float64});
158 try std.testing.expect(!pair.eql(&shorter));
159 const lanes4 = ValueType{ .vector = .{ .element = .f32, .lanes = 4 } };
160 const lanes8 = ValueType{ .vector = .{ .element = .f32, .lanes = 8 } };
161 const four = try Signature.init(&.{}, &.{lanes4});
162 const eight = try Signature.init(&.{}, &.{lanes8});
163 try std.testing.expect(!four.eql(&eight));
164 }
165
166 test "signatures refuse more types than they hold and empty vectors" {
167 const many: [Signature.max_parameters + 1]ValueType = @splat(.memref);
168 try std.testing.expectError(error.TooManyParameters, Signature.init(&many, &.{}));
169 const results = many[0 .. Signature.max_results + 1];
170 try std.testing.expectError(error.TooManyResults, Signature.init(&.{}, results));
171 const empty = ValueType{ .vector = .{ .element = .u8, .lanes = 0 } };
172 try std.testing.expectError(error.InvalidValueType, Signature.init(&.{empty}, &.{}));
173 const wide = ValueType{ .vector = .{ .element = .u8, .lanes = VectorType.max_lanes + 1 } };
174 try std.testing.expectError(error.InvalidValueType, Signature.init(&.{}, &.{wide}));
175
176 const parameters = many[0..Signature.max_parameters];
177 const full = try Signature.init(parameters, results[0..Signature.max_results]);
178 try std.testing.expectEqual(@as(usize, Signature.max_parameters), full.parameters().len);
179 try std.testing.expectEqual(@as(usize, Signature.max_results), full.results().len);
180 }
181
182 test "signatures changed after init report invalid counts and lanes" {
183 const lanes4 = ValueType{ .vector = .{ .element = .f32, .lanes = 4 } };
184 const valid = try Signature.init(&.{.memref}, &.{lanes4});
185 try std.testing.expect(valid.isValid());
186
187 var no_lanes = valid;
188 no_lanes.result_types[0].vector.lanes = 0;
189 try std.testing.expect(!no_lanes.isValid());
190 var too_many = valid;
191 too_many.parameter_count = Signature.max_parameters + 1;
192 try std.testing.expect(!too_many.isValid());
193 var unrecorded = valid;
194 unrecorded.parameter_types[Signature.max_parameters - 1] = no_lanes.result_types[0];
195 try std.testing.expect(unrecorded.isValid());
196 }