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(¶meters, &.{.{ .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 }