lib/accy/src/preparation/product.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const choir = @import("choir");
3
4 const accy_choir = @import("../choir/root.zig");
5 const target_product = @import("../target/root.zig");
6 const run_mod = @import("run.zig");
7
8 const contract = accy_choir.contract;
9 const dispatch = accy_choir.dispatch;
10 const kernel_product = accy_choir.gpu;
11 const memory_product = accy_choir.memory;
12 const semantic = accy_choir.semantic;
13 const tensor = accy_choir.tensor;
14 const publication = accy_choir.publication;
15 const revision = choir.product.revision;
16
17 /// A caller holds this as the finished result of one compile, to read any stage or to launch the
18 /// final one: the result holds one sealed stage record for each of the seven stages, and each is
19 /// checked to belong to its stage and to depend on the stage before it. The result holds no mutable
20 /// compiler job, so nothing can change a stage after the result is made. `retain` returns another
21 /// owner of the same records, and `deinit` releases this owner's references.
22 pub const BackendPreparedModule = opaque {
23 const Data = struct {
24 allocator: std.mem.Allocator,
25 records: [7]*const revision.Revision,
26 };
27
28 const stages = [_]publication.Stage{
29 .semantic, .contract, .tensor, .dispatch, .memory, .kernel, .target,
30 };
31
32 pub fn create(
33 allocator: std.mem.Allocator,
34 records: [7]*const revision.Revision,
35 ) !*BackendPreparedModule {
36 inline for (stages, 0..) |selected, index| {
37 const item = records[index];
38 if (!std.mem.eql(u8, item.address().stage, selected.name())) return error.WrongStage;
39 try item.requireGates(&.{ choir.product.operation.schema_identity, selected.schema() });
40 if (index != 0) try requirePredecessor(item, records[index - 1]);
41 }
42 const owned = try allocator.create(Data);
43 errdefer allocator.destroy(owned);
44 var retained: usize = 0;
45 errdefer for (records[0..retained]) |item| item.release();
46 for (records) |item| {
47 _ = try item.retain();
48 retained += 1;
49 }
50 owned.* = .{ .allocator = allocator, .records = records };
51 return @ptrCast(owned);
52 }
53
54 fn requirePredecessor(item: *const revision.Revision, previous: *const revision.Revision) !void {
55 item.requireDependency("source", previous) catch |err| switch (err) {
56 error.UndeclaredProductInput => return error.UnboundProductInput,
57 };
58 }
59
60 fn data(self: *const BackendPreparedModule) *const Data {
61 return @ptrCast(@alignCast(self));
62 }
63
64 pub fn deinit(self: *BackendPreparedModule) void {
65 const owned = self.data();
66 for (owned.records) |item| item.release();
67 owned.allocator.destroy(@constCast(owned));
68 }
69
70 pub fn retain(
71 self: *const BackendPreparedModule,
72 allocator: std.mem.Allocator,
73 ) !*BackendPreparedModule {
74 return create(allocator, self.data().records);
75 }
76
77 /// A caller reads one sealed stage record, for example the final one to emit device code: the
78 /// call returns the record for `selected`. The record is borrowed from this result, and a
79 /// caller that needs it after the result is released retains it.
80 pub fn stage(
81 self: *const BackendPreparedModule,
82 comptime selected: publication.Stage,
83 ) *const revision.Revision {
84 return self.data().records[@backingInt(selected)];
85 }
86
87 pub fn productKeys(self: *const BackendPreparedModule) BackendPreparationProductKeys {
88 return .{
89 .semantic = choir.product.incremental.productKey(self.stage(.semantic).metadata()),
90 .contract = choir.product.incremental.productKey(self.stage(.contract).metadata()),
91 .tensor = choir.product.incremental.productKey(self.stage(.tensor).metadata()),
92 .dispatch = choir.product.incremental.productKey(self.stage(.dispatch).metadata()),
93 .memory = choir.product.incremental.productKey(self.stage(.memory).metadata()),
94 .kernel = choir.product.incremental.productKey(self.stage(.kernel).metadata()),
95 .target = choir.product.incremental.productKey(self.stage(.target).metadata()),
96 };
97 }
98
99 pub fn productGraph(
100 self: *const BackendPreparedModule,
101 allocator: std.mem.Allocator,
102 ) !choir.product.incremental.ProductGraph {
103 return graphFromKeys(allocator, self.productKeys());
104 }
105 };
106
107 pub const BackendPreparationProductStamps = struct {
108 semantic: ?choir.product.incremental.ProductStamp = null,
109 contract: choir.product.incremental.ProductStamp,
110 tensor: choir.product.incremental.ProductStamp,
111 dispatch: choir.product.incremental.ProductStamp,
112 memory: choir.product.incremental.ProductStamp,
113 kernel: choir.product.incremental.ProductStamp,
114 target: choir.product.incremental.ProductStamp,
115 };
116
117 pub const BackendPreparationProductKeys = struct {
118 semantic: ?choir.product.incremental.ProductKey = null,
119 contract: choir.product.incremental.ProductKey,
120 tensor: choir.product.incremental.ProductKey,
121 dispatch: choir.product.incremental.ProductKey,
122 memory: choir.product.incremental.ProductKey,
123 kernel: choir.product.incremental.ProductKey,
124 target: choir.product.incremental.ProductKey,
125 };
126
127 /// A caller turns one run's plan summaries into stamps to display beside the run: the function
128 /// builds one run stamp, the per-stage display record built for people reading a run, from the
129 /// summaries recorded in `run`, and leaves the first stage's stamp null when the run has no summary
130 /// for it. The stamps are for display, and no key or reuse decision reads them.
131 pub fn stampsFromRun(run: run_mod.BackendPreparationRun) BackendPreparationProductStamps {
132 return .{
133 .semantic = if (run.semantic_fingerprint) |fingerprint|
134 choir.product.incremental.productStamp(semantic.product_name, fingerprint)
135 else
136 null,
137 .contract = choir.product.incremental.productStamp(contract.product_name, run.contract_fingerprint),
138 .tensor = choir.product.incremental.productStamp(tensor.product_name, run.tensor_fingerprint),
139 .dispatch = choir.product.incremental.productStamp(dispatch.product_name, run.dispatch_fingerprint),
140 .memory = choir.product.incremental.productStamp(memory_product.product_name, run.memory_fingerprint),
141 .kernel = choir.product.incremental.productStamp(kernel_product.product_name, run.kernel_fingerprint),
142 .target = choir.product.incremental.productStamp(target_product.product_name, run.target_fingerprint),
143 };
144 }
145
146 pub fn graphFromKeys(
147 allocator: std.mem.Allocator,
148 keys: BackendPreparationProductKeys,
149 ) !choir.product.incremental.ProductGraph {
150 var products: [7]choir.product.incremental.ProductKey = undefined;
151 var count: usize = 0;
152 if (keys.semantic) |semantic_key| {
153 products[count] = semantic_key;
154 count += 1;
155 }
156 products[count] = keys.contract;
157 count += 1;
158 products[count] = keys.tensor;
159 count += 1;
160 products[count] = keys.dispatch;
161 count += 1;
162 products[count] = keys.memory;
163 count += 1;
164 products[count] = keys.kernel;
165 count += 1;
166 products[count] = keys.target;
167 count += 1;
168 return try choir.product.incremental.ProductGraph.initLinear(allocator, products[0..count]);
169 }