lib/choir/src/composition/module/composition.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const choir = @import("../../root.zig");
  3 const composition = @import("../root.zig");
  4 const model = @import("root.zig");
  5 const verification = @import("verification/root.zig");
  6 
  7 const Allocator = std.mem.Allocator;
  8 const Artifact = choir.backends.artifact.Artifact;
  9 const backends = choir.backends;
 10 const bytecode = choir.bytecode;
 11 const product = choir.product;
 12 const source = composition.source;
 13 
 14 pub const VerificationError = verification.Error;
 15 
 16 pub const DiagnosticPath = struct {
 17     source: *const source.Provenance,
 18     variant: *const model.CompositionVariant,
 19     partition: *const source.Partition,
 20     pipeline_input: product.ProductKey,
 21     fragment: *const model.Fragment,
 22     fragment_export: *const model.Export,
 23     call_site: *const model.CallSite,
 24     artifact: *const Artifact,
 25 };
 26 
 27 /// Owns the serializable whole-program product: source contracts, fragments, and variants.
 28 /// `init` clones its source module so the result has one independent lifetime.
 29 pub const CompositionModule = struct {
 30     allocator: Allocator,
 31     source_module: source.PartitionedModule,
 32     fragments: std.ArrayList(model.Fragment) = .empty,
 33     variants: std.ArrayList(model.CompositionVariant) = .empty,
 34 
 35     pub fn init(allocator: Allocator, partitioned: *const source.PartitionedModule) Allocator.Error!CompositionModule {
 36         return .{
 37             .allocator = allocator,
 38             .source_module = try partitioned.clone(allocator),
 39         };
 40     }
 41 
 42     pub fn deinit(self: *CompositionModule) void {
 43         var variant_index = self.variants.items.len;
 44         while (variant_index != 0) {
 45             variant_index -= 1;
 46             self.variants.items[variant_index].deinit(self.allocator);
 47         }
 48         self.variants.deinit(self.allocator);
 49         var fragment_index = self.fragments.items.len;
 50         while (fragment_index != 0) {
 51             fragment_index -= 1;
 52             self.fragments.items[fragment_index].deinit(self.allocator);
 53         }
 54         self.fragments.deinit(self.allocator);
 55         self.source_module.deinit();
 56         self.* = undefined;
 57     }
 58 
 59     pub fn addFragment(self: *CompositionModule, spec: model.FragmentSpec) (Allocator.Error || VerificationError)!void {
 60         if (self.fragment(spec.id) != null) return error.DuplicateFragment;
 61         const partition_value = self.source_module.partition(spec.partition) orelse return error.MissingPartition;
 62         if (partition_value.pipeline != spec.pipeline) return error.PipelineMismatch;
 63         if (!partition_value.product.eql(spec.pipeline_input.ref)) return error.ProductIdentityMismatch;
 64         try verification.verifyFragmentSpec(spec);
 65         var fragment_value = try model.Fragment.init(self.allocator, spec);
 66         errdefer fragment_value.deinit(self.allocator);
 67         try self.fragments.append(self.allocator, fragment_value);
 68     }
 69 
 70     pub fn addVariant(self: *CompositionModule, spec: model.CompositionVariantSpec) (Allocator.Error || VerificationError)!void {
 71         try verification.verifyCompositionVariantSpec(self, spec);
 72         var variant_value = try model.CompositionVariant.init(self.allocator, spec);
 73         errdefer variant_value.deinit(self.allocator);
 74         try self.variants.append(self.allocator, variant_value);
 75     }
 76 
 77     pub fn verify(self: *const CompositionModule) VerificationError!void {
 78         try verification.verifyModule(self);
 79     }
 80 
 81     pub fn fragment(self: *const CompositionModule, id: source.FragmentId) ?*const model.Fragment {
 82         for (self.fragments.items) |*fragment_value| {
 83             if (fragment_value.id.value == id.value) return fragment_value;
 84         }
 85         return null;
 86     }
 87 
 88     pub fn variant(self: *const CompositionModule, id: model.CompositionVariantId) ?*const model.CompositionVariant {
 89         for (self.variants.items) |*variant_value| {
 90             if (variant_value.id.value == id.value) return variant_value;
 91         }
 92         return null;
 93     }
 94 
 95     pub fn selectVariant(self: *const CompositionModule, choice: model.TargetChoiceSpec) ?*const model.CompositionVariant {
 96         for (self.variants.items) |*variant_value| {
 97             if (variant_value.choice.eql(choice)) return variant_value;
 98         }
 99         return null;
100     }
101 
102     pub fn diagnosticPath(
103         self: *const CompositionModule,
104         variant_id: model.CompositionVariantId,
105         id: source.CallSiteId,
106     ) VerificationError!DiagnosticPath {
107         const variant_value = self.variant(variant_id) orelse return error.MissingCompositionVariant;
108         const call_site = variant_value.callSite(id) orelse return error.MissingSemanticCallSite;
109         const fragment_value = self.fragment(call_site.callee) orelse return error.MissingFragment;
110         const partition_value = self.source_module.partition(fragment_value.partition) orelse return error.MissingPartition;
111         const semantic = self.source_module.callSite(id) orelse return error.MissingSemanticCallSite;
112         const export_value = fragment_value.exportByName(call_site.callee_export) orelse return error.MissingExport;
113         const artifact_value = artifactProviding(fragment_value, export_value.symbol) orelse return error.MissingProvidedSymbol;
114         return .{
115             .source = &semantic.provenance,
116             .variant = variant_value,
117             .partition = partition_value,
118             .pipeline_input = fragment_value.pipeline_input,
119             .fragment = fragment_value,
120             .fragment_export = export_value,
121             .call_site = call_site,
122             .artifact = artifact_value,
123         };
124     }
125 
126     pub fn bytecodeResourceCount(self: *const CompositionModule) usize {
127         var count: usize = 0;
128         for (self.fragments.items) |fragment_value| {
129             for (fragment_value.artifacts) |artifact_value| count += backends.artifact.resource.count(artifact_value);
130         }
131         return count;
132     }
133 
134     pub fn writeBytecodeResources(
135         self: *const CompositionModule,
136         out: []bytecode.Resource,
137     ) backends.artifact.resource.Error![]const bytecode.Resource {
138         const required = self.bytecodeResourceCount();
139         if (out.len < required) return error.NoSpaceLeft;
140         var offset: usize = 0;
141         for (self.fragments.items) |fragment_value| {
142             for (fragment_value.artifacts) |artifact_value| {
143                 const resources = try backends.artifact.resource.write(artifact_value, out[offset..]);
144                 offset += resources.len;
145             }
146         }
147         return out[0..offset];
148     }
149 };
150 
151 fn artifactProviding(fragment_value: *const model.Fragment, symbol: []const u8) ?*const Artifact {
152     for (fragment_value.artifacts) |*artifact_value| {
153         if (artifact_value.linkage.hasProvided(symbol)) return artifact_value;
154     }
155     return null;
156 }