lib/choir/src/backends/artifact/resource.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const artifact_root = @import("root.zig");
3 const bytecode = @import("../../bytecode/root.zig");
4
5 pub fn count(artifact: artifact_root.Artifact) usize {
6 return artifact.payload.buffers.items.len + artifact.payload.text_dumps.items.len + artifact.payload.debug_records.items.len;
7 }
8
9 pub const Error = error{NoSpaceLeft};
10
11 pub fn write(
12 artifact: artifact_root.Artifact,
13 out: []bytecode.Resource,
14 ) Error![]const bytecode.Resource {
15 const required = count(artifact);
16 if (out.len < required) return error.NoSpaceLeft;
17
18 var index: usize = 0;
19 for (artifact.payload.buffers.items) |buffer| {
20 out[index] = .{
21 .namespace = "choir.artifact.buffer",
22 .name = buffer.name,
23 .type_id = typeIdForBufferFormat(buffer.format),
24 .data = buffer.bytes,
25 };
26 index += 1;
27 }
28 for (artifact.payload.text_dumps.items) |dump| {
29 out[index] = .{
30 .namespace = "choir.artifact.text-dump",
31 .name = dump.name,
32 .type_id = "choir.artifact.TextDump/v1",
33 .data = dump.contents,
34 };
35 index += 1;
36 }
37 for (artifact.payload.debug_records.items) |record| {
38 out[index] = .{
39 .namespace = "choir.artifact.debug",
40 .name = record.name,
41 .type_id = typeIdForDebugRecord(record.kind),
42 .data = record.bytes,
43 };
44 index += 1;
45 }
46 return out[0..index];
47 }
48
49 fn typeIdForBufferFormat(format: artifact_root.BufferFormat) []const u8 {
50 return switch (format) {
51 .raw => "choir.artifact.Buffer.raw/v1",
52 .machine_code => "choir.artifact.Buffer.machine-code/v1",
53 .object_file => "choir.artifact.Buffer.object-file/v1",
54 .webassembly_module => "choir.artifact.Buffer.webassembly-module/v1",
55 .assembly => "choir.artifact.Buffer.assembly/v1",
56 .source_text => "choir.artifact.Buffer.source-text/v1",
57 .debug_section => "choir.artifact.Buffer.debug-section/v1",
58 .vendor => "choir.artifact.Buffer.vendor/v1",
59 };
60 }
61
62 fn typeIdForDebugRecord(kind: artifact_root.DebugRecordKind) []const u8 {
63 return switch (kind) {
64 .line_table => "choir.artifact.DebugRecord.line-table/v1",
65 .symbol_table => "choir.artifact.DebugRecord.symbol-table/v1",
66 .dwarf_section => "choir.artifact.DebugRecord.dwarf-section/v1",
67 .source_map => "choir.artifact.DebugRecord.source-map/v1",
68 .note => "choir.artifact.DebugRecord.note/v1",
69 };
70 }
71
72 test "artifact exposes payload bytecode resources" {
73 var artifact = try artifact_root.Artifact.init(std.testing.allocator, .{ .kind = .container });
74 defer artifact.deinit();
75
76 try artifact.payload.addBuffer(.{
77 .name = "kernel.o",
78 .format = .object_file,
79 .bytes = &.{ 0x7f, 'E', 'L', 'F' },
80 });
81 try artifact.payload.addTextDump(.{
82 .name = "manifest",
83 .contents = "target=x86_64",
84 });
85 try artifact.payload.addDebugRecord(.{
86 .name = ".debug_line",
87 .kind = .line_table,
88 .bytes = &.{ 1, 2, 3 },
89 });
90
91 try std.testing.expectEqual(@as(usize, 3), count(artifact));
92 const storage = try std.testing.allocator.alloc(bytecode.Resource, count(artifact));
93 defer std.testing.allocator.free(storage);
94
95 const resources = try write(artifact, storage);
96 try std.testing.expectEqual(@as(usize, 3), resources.len);
97
98 try std.testing.expectEqualStrings("choir.artifact.buffer", resources[0].namespace);
99 try std.testing.expectEqualStrings("kernel.o", resources[0].name);
100 try std.testing.expectEqualStrings("choir.artifact.Buffer.object-file/v1", resources[0].type_id);
101 try std.testing.expectEqualSlices(u8, &.{ 0x7f, 'E', 'L', 'F' }, resources[0].data);
102
103 try std.testing.expectEqualStrings("choir.artifact.text-dump", resources[1].namespace);
104 try std.testing.expectEqualStrings("manifest", resources[1].name);
105 try std.testing.expectEqualStrings("choir.artifact.TextDump/v1", resources[1].type_id);
106 try std.testing.expectEqualStrings("target=x86_64", resources[1].data);
107
108 try std.testing.expectEqualStrings("choir.artifact.debug", resources[2].namespace);
109 try std.testing.expectEqualStrings(".debug_line", resources[2].name);
110 try std.testing.expectEqualStrings("choir.artifact.DebugRecord.line-table/v1", resources[2].type_id);
111 try std.testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, resources[2].data);
112
113 try std.testing.expectError(error.NoSpaceLeft, write(artifact, storage[0..2]));
114 }
115
116 test "artifact bytecode resources round-trip with a module" {
117 const ir = @import("../../core/root.zig");
118 const test_dialect = @import("../../dialects/fixture/root.zig");
119
120 var artifact = try artifact_root.Artifact.init(std.testing.allocator, .{ .kind = .container });
121 defer artifact.deinit();
122 try artifact.payload.addBuffer(.{
123 .name = "kernel.ptx",
124 .format = .source_text,
125 .bytes = ".version 6.0\n",
126 });
127 try artifact.payload.addTextDump(.{
128 .name = "manifest",
129 .contents = "target=nvptx",
130 });
131
132 const resource_storage = try std.testing.allocator.alloc(bytecode.Resource, count(artifact));
133 defer std.testing.allocator.free(resource_storage);
134 const resources = try write(artifact, resource_storage);
135
136 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
137 defer ctx.deinit(std.testing.allocator);
138 try @import("../../dialects/root.zig").registerAllDialects(&ctx);
139 const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
140
141 const bytes = try bytecode.encodeModuleWithResources(std.testing.allocator, module_op.op, resources);
142 defer std.testing.allocator.free(bytes);
143
144 const header = try bytecode.inspectHeader(bytes);
145 try std.testing.expectEqual(bytecode.format_version, header.version);
146 try std.testing.expect(header.compatibility().readable());
147
148 var decode_ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
149 defer decode_ctx.deinit(std.testing.allocator);
150 try test_dialect.registerTestDialect(&decode_ctx);
151
152 var decoded = try bytecode.decodeModule(std.testing.allocator, &decode_ctx, bytes);
153 defer decoded.deinit();
154
155 try std.testing.expect(decoded.format().readable());
156 try std.testing.expectEqualStrings("test.module", decoded.module.name.name);
157 try std.testing.expectEqual(resources.len, decoded.resources.len);
158 for (resources, decoded.resources) |expected, actual| {
159 try std.testing.expectEqualStrings(expected.namespace, actual.namespace);
160 try std.testing.expectEqualStrings(expected.name, actual.name);
161 try std.testing.expectEqualStrings(expected.type_id, actual.type_id);
162 try std.testing.expectEqualSlices(u8, expected.data, actual.data);
163 }
164 }