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 }