lib/choir/src/backends/artifact/construction.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const artifact_root = @import("root.zig");
  3 
  4 const Allocator = std.mem.Allocator;
  5 
  6 /// Read-only bytes that a machine-code artifact carries beside its code.
  7 /// The artifact stores them as a raw buffer and provides them as a data symbol of the same name.
  8 pub const DataBuffer = struct {
  9     name: []const u8,
 10     bytes: []const u8,
 11     alignment: usize = 1,
 12     binding: artifact_root.SymbolBinding = .local,
 13 };
 14 
 15 /// Builds an artifact for one function whose parameter and result types are `signature`.
 16 /// The code buffer comes first, followed by one raw buffer for each entry of `data`.
 17 pub fn machineCodeArtifact(
 18     allocator: Allocator,
 19     target: artifact_root.Target,
 20     abi: artifact_root.Abi,
 21     entry: []const u8,
 22     signature: artifact_root.Signature,
 23     code: []const u8,
 24     data: []const DataBuffer,
 25     relocations: []const artifact_root.Relocation,
 26 ) Allocator.Error!artifact_root.Artifact {
 27     var artifact = try artifact_root.Artifact.init(allocator, .{
 28         .kind = .machine_code,
 29         .target = target,
 30         .abi = abi,
 31     });
 32     errdefer artifact.deinit();
 33 
 34     try artifact.payload.addBuffer(.{
 35         .name = entry,
 36         .format = .machine_code,
 37         .bytes = code,
 38         .alignment = 16,
 39     });
 40     try artifact.linkage.addProvided(.{
 41         .name = entry,
 42         .kind = .function,
 43         .binding = .external,
 44         .signature = signature,
 45     });
 46     for (data) |buffer| {
 47         try artifact.payload.addBuffer(.{
 48             .name = buffer.name,
 49             .format = .raw,
 50             .bytes = buffer.bytes,
 51             .alignment = buffer.alignment,
 52         });
 53         try artifact.linkage.addProvided(.{
 54             .name = buffer.name,
 55             .kind = .data,
 56             .binding = buffer.binding,
 57         });
 58     }
 59     for (relocations) |relocation| {
 60         try artifact.linkage.addRelocation(relocation);
 61         if (relocation.kind == .call and !artifact.linkage.hasRequired(relocation.symbol)) {
 62             try artifact.linkage.addRequired(.{
 63                 .name = relocation.symbol,
 64                 .kind = .function,
 65                 .binding = .external,
 66             });
 67         }
 68     }
 69     return artifact;
 70 }
 71 
 72 pub fn objectFileArtifact(
 73     allocator: Allocator,
 74     target: artifact_root.Target,
 75     abi: artifact_root.Abi,
 76     name: []const u8,
 77     object: []const u8,
 78     relocations: []const artifact_root.Relocation,
 79 ) Allocator.Error!artifact_root.Artifact {
 80     var artifact = try artifact_root.Artifact.init(allocator, .{
 81         .kind = .object_file,
 82         .target = target,
 83         .abi = abi,
 84     });
 85     errdefer artifact.deinit();
 86 
 87     try artifact.payload.addBuffer(.{
 88         .name = name,
 89         .format = .object_file,
 90         .bytes = object,
 91         .alignment = 8,
 92     });
 93     try artifact.linkage.addProvided(.{
 94         .name = name,
 95         .kind = .function,
 96         .binding = .external,
 97     });
 98     for (relocations) |relocation| {
 99         try artifact.linkage.addRelocation(relocation);
100         if (relocation.kind == .call and !artifact.linkage.hasRequired(relocation.symbol)) {
101             try artifact.linkage.addRequired(.{
102                 .name = relocation.symbol,
103                 .kind = .function,
104                 .binding = .external,
105             });
106         }
107     }
108     return artifact;
109 }
110 
111 pub fn webassemblyModuleArtifact(
112     allocator: Allocator,
113     name: []const u8,
114     module: []const u8,
115 ) Allocator.Error!artifact_root.Artifact {
116     var artifact = try artifact_root.Artifact.init(allocator, .{
117         .kind = .webassembly_module,
118         .target = .{
119             .architecture = .wasm,
120             .triple = "wasm32-unknown-unknown",
121         },
122         .abi = .{
123             .name = "wasm32",
124             .object_format = "wasm",
125             .pointer_width_bits = 32,
126             .endianness = .little,
127         },
128     });
129     errdefer artifact.deinit();
130 
131     try artifact.payload.addBuffer(.{
132         .name = name,
133         .format = .webassembly_module,
134         .bytes = module,
135         .alignment = 1,
136     });
137     return artifact;
138 }
139 
140 test "artifact records machine code metadata without x86-specific structs" {
141     const relocations = [_]artifact_root.Relocation{
142         .{ .offset = 4, .symbol = "runtime_entry", .kind = .call },
143     };
144     const parameters = [_]artifact_root.ValueType{ .{ .scalar = .i32 }, .memref };
145     const signature = try artifact_root.Signature.init(&parameters, &.{.{ .scalar = .f64 }});
146 
147     var artifact = try machineCodeArtifact(
148         std.testing.allocator,
149         .{
150             .architecture = .x86_64,
151             .triple = "x86_64-unknown-linux-gnu",
152             .cpu = "x86-64-v3",
153             .features = &.{ "sse2", "avx2" },
154         },
155         .{
156             .name = "sysv",
157             .calling_convention = "c",
158             .object_format = "elf",
159             .pointer_width_bits = 64,
160             .endianness = .little,
161         },
162         "main",
163         signature,
164         &.{ 0x55, 0x48, 0x89, 0xe5 },
165         &.{.{ .name = "table", .bytes = "\x01\x02\x03", .alignment = 8 }},
166         &relocations,
167     );
168     defer artifact.deinit();
169 
170     try artifact.options.add(.{ .key = "pic", .value = .{ .flag = true } });
171     try artifact.linkage.addRequired(.{ .name = "runtime_entry", .kind = .function });
172     try artifact.payload.addDebugRecord(.{
173         .name = ".debug_line",
174         .kind = .line_table,
175         .bytes = &.{ 1, 2, 3 },
176     });
177     try artifact.payload.addTextDump(.{ .name = "disassembly", .contents = "push rbp" });
178     try artifact.verification.replace(artifact.allocator, .{
179         .state = .passed,
180         .stage = "backend/x86_64/verify",
181     });
182 
183     try std.testing.expectEqual(artifact_root.ArtifactKind.machine_code, artifact.metadata.kind);
184     try std.testing.expectEqual(artifact_root.Architecture.x86_64, artifact.metadata.target.architecture);
185     try std.testing.expectEqualStrings("x86-64-v3", artifact.metadata.target.cpu.?);
186     try std.testing.expectEqualStrings("avx2", artifact.metadata.target.features[1]);
187     try std.testing.expectEqual(@as(u16, 64), artifact.metadata.abi.pointer_width_bits.?);
188     try std.testing.expectEqual(artifact_root.Endianness.little, artifact.metadata.abi.endianness.?);
189     const buffers = artifact.payload.buffers.items;
190     try std.testing.expectEqual(@as(usize, 2), buffers.len);
191     try std.testing.expectEqual(artifact_root.BufferFormat.machine_code, buffers[0].format);
192     try std.testing.expectEqual(artifact_root.BufferFormat.raw, buffers[1].format);
193     try std.testing.expectEqualStrings("table", buffers[1].name);
194     try std.testing.expectEqualSlices(u8, "\x01\x02\x03", buffers[1].bytes);
195     try std.testing.expectEqual(@as(usize, 8), buffers[1].alignment);
196     const provided = artifact.linkage.provided_symbols.items;
197     try std.testing.expectEqual(@as(usize, 2), provided.len);
198     try std.testing.expect(artifact.linkage.hasProvided("main"));
199     try std.testing.expect(provided[0].signature.?.eql(&signature));
200     try std.testing.expectEqual(@as(?artifact_root.Signature, null), provided[1].signature);
201     try std.testing.expectEqual(artifact_root.SymbolKind.data, provided[1].kind);
202     try std.testing.expectEqual(artifact_root.SymbolBinding.local, provided[1].binding);
203     try std.testing.expectEqual(@as(usize, 1), artifact.linkage.relocations.items.len);
204     try std.testing.expectEqual(artifact_root.RelocationKind.call, artifact.linkage.relocations.items[0].kind);
205     try std.testing.expect(artifact.linkage.hasRequired("runtime_entry"));
206     try std.testing.expectEqual(artifact_root.VerificationState.passed, artifact.verification.state);
207 }
208 
209 test "artifact records object file buffers and required symbols" {
210     const relocations = [_]artifact_root.Relocation{
211         .{ .offset = 8, .symbol = "__tiny_runtime_call", .kind = .call },
212         .{ .offset = 24, .symbol = "__tiny_runtime_call", .kind = .call },
213         .{ .offset = 40, .symbol = ".Ltiny_literal_0", .kind = .absolute },
214     };
215 
216     var artifact = try objectFileArtifact(
217         std.testing.allocator,
218         .{
219             .architecture = .x86_64,
220             .triple = "x86_64-unknown-linux-gnu",
221         },
222         .{
223             .name = "sysv",
224             .calling_convention = "c",
225             .object_format = "elf",
226             .pointer_width_bits = 64,
227             .endianness = .little,
228         },
229         "tiny_entry",
230         &.{ 0x7f, 'E', 'L', 'F' },
231         &relocations,
232     );
233     defer artifact.deinit();
234 
235     try std.testing.expectEqual(artifact_root.ArtifactKind.object_file, artifact.metadata.kind);
236     try std.testing.expectEqual(@as(usize, 1), artifact.payload.buffers.items.len);
237     try std.testing.expectEqual(artifact_root.BufferFormat.object_file, artifact.payload.buffers.items[0].format);
238     try std.testing.expectEqual(@as(usize, 3), artifact.linkage.relocations.items.len);
239     try std.testing.expectEqual(artifact_root.RelocationKind.absolute, artifact.linkage.relocations.items[2].kind);
240     try std.testing.expectEqual(@as(usize, 1), artifact.linkage.required_symbols.items.len);
241     try std.testing.expect(artifact.linkage.hasRequired("__tiny_runtime_call"));
242     try std.testing.expectEqual(@as(usize, 1), artifact.linkage.provided_symbols.items.len);
243     try std.testing.expect(artifact.linkage.hasProvided("tiny_entry"));
244 }
245 
246 test "artifact records webassembly module buffers" {
247     var artifact = try webassemblyModuleArtifact(
248         std.testing.allocator,
249         "module",
250         &.{ 0x00, 0x61, 0x73, 0x6d },
251     );
252     defer artifact.deinit();
253 
254     try std.testing.expectEqual(artifact_root.ArtifactKind.webassembly_module, artifact.metadata.kind);
255     try std.testing.expectEqual(artifact_root.Architecture.wasm, artifact.metadata.target.architecture);
256     try std.testing.expectEqual(@as(u16, 32), artifact.metadata.abi.pointer_width_bits.?);
257     try std.testing.expectEqual(@as(usize, 1), artifact.payload.buffers.items.len);
258     try std.testing.expectEqual(artifact_root.BufferFormat.webassembly_module, artifact.payload.buffers.items[0].format);
259 }