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 }