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 }