lib/choir/src/composition/module/serialization/reference.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const product = @import("../../../product/root.zig");
3 const serialization = @import("root.zig");
4 const format = serialization.format;
5
6 /// Distinct compiler manifests in order of first use. A record names one by
7 /// index, so a stream carries each manifest once and mirrors the interning the
8 /// import store performs, instead of repeating those bytes per record.
9 pub const Manifests = struct {
10 values: [format.max_compiler_manifests][]const u8 = undefined,
11 count: usize = 0,
12
13 pub fn intern(self: *Manifests, bytes: []const u8) format.WriteError!usize {
14 std.debug.assert(self.count <= self.values.len);
15 for (self.values[0..self.count], 0..) |value, index| {
16 if (std.mem.eql(u8, value, bytes)) return index;
17 }
18 if (self.count == self.values.len) return error.LimitExceeded;
19 self.values[self.count] = bytes;
20 self.count += 1;
21 return self.count - 1;
22 }
23
24 pub fn at(self: *const Manifests, index: usize) serialization.DeserializeError![]const u8 {
25 if (index >= self.count) return error.InvalidProductRecord;
26 return self.values[index];
27 }
28 };
29
30 pub fn writeManifests(
31 writer: *format.Writer,
32 manifests: *const Manifests,
33 ) serialization.SerializeError!void {
34 try writer.writeCount(manifests.count);
35 for (manifests.values[0..manifests.count]) |bytes| try writer.writeBlob(bytes);
36 }
37
38 pub fn readManifests(reader: *format.Reader) serialization.DeserializeError!Manifests {
39 var manifests = Manifests{};
40 const count = try reader.readCount();
41 if (count > format.max_compiler_manifests) return error.LimitExceeded;
42 for (0..count) |index| manifests.values[index] = try reader.readBlob();
43 manifests.count = count;
44 return manifests;
45 }
46
47 pub fn createStore(
48 allocator: std.mem.Allocator,
49 fragments: usize,
50 ) serialization.DeserializeError!*product.revision.Store {
51 if (fragments > format.max_collection_entries) return error.LimitExceeded;
52 const owner = product.revision.Store.create(allocator, .{
53 .revisions = @intCast(@max(fragments, 1)),
54 .kinds = 1,
55 .builders = 1,
56 .compiler_manifests = format.max_compiler_manifests,
57 .record_bytes = format.max_blob_bytes,
58 .gate_scratch_bytes = 0,
59 .candidate_count = 1,
60 .screening_bytes = 0,
61 }) catch |err| switch (err) {
62 error.OutOfMemory => return error.OutOfMemory,
63 else => return error.ProductRecordCapacityExceeded,
64 };
65 owner.freeze();
66 return owner;
67 }
68
69 pub fn writeAddress(
70 writer: *format.Writer,
71 address: product.ProductRef,
72 ) serialization.SerializeError!void {
73 inline for (@typeInfo(product.ProductRef).@"struct".field_names) |field| {
74 try writer.writeString(@field(address, field));
75 }
76 }
77
78 pub fn readAddress(reader: *format.Reader) serialization.DeserializeError!product.ProductRef {
79 var address: product.ProductRef = undefined;
80 inline for (@typeInfo(product.ProductRef).@"struct".field_names) |field| {
81 @field(address, field) = try reader.readString();
82 }
83 return address;
84 }
85
86 /// The result borrows metadata from the store. Deserialization cannot recreate
87 /// a sealed revision or the in-process entitlement attached to its receipt.
88 pub fn readRecord(
89 owner: *product.revision.Store,
90 reader: *format.Reader,
91 manifests: *const Manifests,
92 ) serialization.DeserializeError!product.ProductKey {
93 const blob = try reader.readBlob();
94 const manifest = try manifests.at(try reader.readInt(u32));
95 const imported = owner.importRecord(blob, manifest, .{
96 .bytes = format.max_blob_bytes,
97 .records = format.max_total_entries,
98 .depth = 64,
99 }) catch |err| switch (err) {
100 error.OutOfMemory => return error.OutOfMemory,
101 else => return error.InvalidProductRecord,
102 };
103 imported.release();
104 return product.productKey(imported);
105 }
106
107 test "composition references preserve exact addresses and reject malformed embedded records" {
108 const allocator = std.testing.allocator;
109 const address = product.productRef("producer", "source", "stage", "variant");
110 var writer = format.Writer.init(allocator);
111 defer writer.deinit();
112 try writeAddress(&writer, address);
113 var address_reader = try format.Reader.init(writer.bytes.items);
114 try std.testing.expect(address.eql(try readAddress(&address_reader)));
115 try std.testing.expect(address_reader.atEnd());
116
117 var manifests = Manifests{};
118 try std.testing.expectEqual(@as(usize, 0), try manifests.intern("toolchain-a"));
119 try std.testing.expectEqual(@as(usize, 0), try manifests.intern("toolchain-a"));
120 try std.testing.expectEqual(@as(usize, 1), try manifests.intern("toolchain-b"));
121 try std.testing.expectError(error.InvalidProductRecord, manifests.at(2));
122
123 var malformed = format.Writer.init(allocator);
124 defer malformed.deinit();
125 try malformed.writeBlob("not-an-exact-record");
126 try malformed.writeInt(u32, 0);
127 var reader = try format.Reader.init(malformed.bytes.items);
128 const owner = try createStore(allocator, 1);
129 defer owner.release();
130 try std.testing.expectError(
131 error.InvalidProductRecord,
132 readRecord(owner, &reader, &manifests),
133 );
134 try std.testing.expectEqual(@as(u32, 0), owner.publicationCount());
135 }
136
137 test "composition references carry each compiler manifest once and refuse an unknown index" {
138 const allocator = std.testing.allocator;
139 var manifests = Manifests{};
140 _ = try manifests.intern("toolchain-a");
141 _ = try manifests.intern("toolchain-b");
142 var writer = format.Writer.init(allocator);
143 defer writer.deinit();
144 try writeManifests(&writer, &manifests);
145 var reader = try format.Reader.init(writer.bytes.items);
146 const restored = try readManifests(&reader);
147 try std.testing.expectEqual(@as(usize, 2), restored.count);
148 try std.testing.expectEqualStrings("toolchain-a", try restored.at(0));
149 try std.testing.expectEqualStrings("toolchain-b", try restored.at(1));
150 try std.testing.expect(reader.atEnd());
151
152 var full = Manifests{};
153 var names: [format.max_compiler_manifests][7]u8 = undefined;
154 for (&names, 0..) |*name, index| {
155 _ = try std.fmt.bufPrint(name, "tc-{d:0>4}", .{index});
156 _ = try full.intern(name);
157 }
158 try std.testing.expectEqual(format.max_compiler_manifests, full.count);
159 try std.testing.expectError(error.LimitExceeded, full.intern("overflow"));
160 }