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 }