lib/choir/src/backends/artifact/serialization/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const artifact = @import("../root.zig");
3 const binary = @import("../../../serialization/root.zig").binary;
4 const serialization = @import("root.zig");
5
6 test {
7 std.testing.refAllDecls(serialization);
8 }
9
10 test "artifact serialization uses stable model-owned tags" {
11 try std.testing.expectEqual(@as(u8, 1), try binary.encodeTag(artifact.ArtifactKind.unknown));
12 try std.testing.expectEqual(@as(u8, 8), try binary.encodeTag(artifact.ArtifactKind.container));
13 try std.testing.expectEqual(@as(u8, 5), try binary.encodeTag(artifact.OptionValueTag.bytes));
14 try std.testing.expectEqual(artifact.BufferFormat.object_file, try binary.decodeTag(artifact.BufferFormat, 3));
15 try std.testing.expectError(error.InvalidTag, binary.decodeTag(artifact.BufferFormat, 0xff));
16 }
17
18 test "artifact binary serialization round-trips every owned concern" {
19 var original = try artifact.Artifact.init(std.testing.allocator, .{
20 .kind = .machine_code,
21 .producer = "choir-x64",
22 .target = .{
23 .architecture = .x86_64,
24 .triple = "x86_64-unknown-linux-gnu",
25 .vendor = "unknown",
26 .os = "linux",
27 .environment = "gnu",
28 .cpu = "x86-64-v3",
29 .features = &.{ "sse2", "+avx2" },
30 },
31 .abi = .{
32 .name = "sysv",
33 .calling_convention = "c",
34 .object_format = "elf",
35 .pointer_width_bits = 64,
36 .endianness = .little,
37 },
38 });
39 defer original.deinit();
40 try original.options.add(.{ .key = "checked", .value = .{ .flag = true } });
41 try original.options.add(.{ .key = "bias", .value = .{ .signed = -17 } });
42 try original.options.add(.{ .key = "seed", .value = .{ .unsigned = 42 } });
43 try original.options.add(.{ .key = "mode", .value = .{ .text = "durable" } });
44 try original.options.add(.{ .key = "opaque", .value = .{ .bytes = &.{ 0, 255, 7 } } });
45 try original.linkage.addRequired(.{ .name = "runtime", .kind = .runtime, .binding = .weak });
46 const lanes = artifact.ValueType{ .vector = .{ .element = .f32, .lanes = 4 } };
47 const signature = try artifact.Signature.init(&.{ .{ .scalar = .u16 }, .memref }, &.{lanes});
48 try original.linkage.addProvided(.{
49 .name = "entry",
50 .kind = .function,
51 .binding = .external,
52 .signature = signature,
53 });
54 try original.linkage.addProvided(.{ .name = "table", .kind = .data, .binding = .local });
55 try original.linkage.addRelocation(.{ .offset = 2, .symbol = "runtime", .kind = .call, .addend = -4, .width_bits = 64 });
56 try original.payload.addDebugRecord(.{ .name = ".debug_line", .kind = .dwarf_section, .bytes = &.{ 1, 0, 2, 0 } });
57 try original.payload.addBuffer(.{ .name = "entry", .format = .machine_code, .bytes = &.{ 0x48, 0xb8, 0xff, 0xd0, 0xc3 }, .alignment = 32 });
58 try original.payload.addTextDump(.{ .name = "lowered", .contents = "func @entry" });
59 try original.verification.replace(original.allocator, .{ .state = .failed, .stage = "machine-verify", .message = "preserved" });
60
61 const encoded = try serialization.serialize(std.testing.allocator, &original);
62 defer std.testing.allocator.free(encoded);
63 var decoded = try serialization.deserialize(std.testing.allocator, encoded);
64 defer decoded.deinit();
65 try std.testing.expect(original.eql(decoded));
66 try std.testing.expect(decoded.linkage.provided_symbols.items[0].signature.?.eql(&signature));
67 try std.testing.expect(decoded.linkage.provided_symbols.items[1].signature == null);
68
69 const reencoded = try serialization.serialize(std.testing.allocator, &decoded);
70 defer std.testing.allocator.free(reencoded);
71 try std.testing.expectEqualSlices(u8, encoded, reencoded);
72 }
73
74 test "artifact linkage deserialization refuses malformed signatures" {
75 const scalar = @backingInt(artifact.ValueKind.scalar);
76 const memref = @backingInt(artifact.ValueKind.memref);
77 const vector = @backingInt(artifact.ValueKind.vector);
78 const f32_tag = @backingInt(artifact.ScalarType.f32);
79 const f64_tag = @backingInt(artifact.ScalarType.f64);
80
81 var linkage = try readSignedSymbol(&.{ 1, 1, memref, vector, f32_tag, 4 });
82 defer linkage.deinit();
83 const lanes = artifact.ValueType{ .vector = .{ .element = .f32, .lanes = 4 } };
84 const signature = try artifact.Signature.init(&.{.memref}, &.{lanes});
85 try std.testing.expect(linkage.provided_symbols.items[0].signature.?.eql(&signature));
86
87 try expectMalformed(error.LimitExceeded, &.{ artifact.Signature.max_parameters + 1, 0 });
88 try expectMalformed(error.LimitExceeded, &.{ 0, artifact.Signature.max_results + 1 });
89 try expectMalformed(error.InvalidTag, &.{ 1, 0, vector + 1 });
90 try expectMalformed(error.InvalidTag, &.{ 1, 0, scalar, f64_tag + 1 });
91 try expectMalformed(error.InvalidValue, &.{ 0, 1, vector, f32_tag, 0 });
92 try expectMalformed(error.InvalidValue, &.{ 0, 1, vector, f32_tag, 17 });
93 }
94
95 fn expectMalformed(expected: anyerror, signature: []const u8) !void {
96 try std.testing.expectError(expected, readSignedSymbol(signature));
97 }
98
99 fn readSignedSymbol(signature: []const u8) !artifact.Linkage {
100 var writer = serialization.format.Writer.init(std.testing.allocator);
101 defer writer.deinit();
102 try writer.writeCount(0);
103 try writer.writeCount(1);
104 try writer.writeString("entry");
105 try writer.writeTag(artifact.SymbolKind.function);
106 try writer.writeTag(artifact.SymbolBinding.external);
107 try writer.writeBool(true);
108 try writer.writeRaw(signature);
109 try writer.writeCount(0);
110
111 var reader = try serialization.format.Reader.init(writer.bytes.items);
112 var linkage = artifact.Linkage.init(std.testing.allocator);
113 errdefer linkage.deinit();
114 try serialization.linkage.readInto(&reader, &linkage);
115 try std.testing.expect(reader.atEnd());
116 return linkage;
117 }