lib/accy/src/validation/composition/host.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const choir = @import("choir");
  3 const namespace = @import("root.zig");
  4 
  5 const Allocator = std.mem.Allocator;
  6 const composition = choir.composition;
  7 const Backend = choir.backends.x86_64.backend.Backend;
  8 const source = namespace.source;
  9 const publication = namespace.fixture;
 10 
 11 pub const Job = struct {
 12     artifact: choir.backends.artifact.Artifact,
 13     input: choir.product.operation.Product,
 14 
 15     pub fn deinit(self: *Job) void {
 16         self.artifact.deinit();
 17         self.input.release();
 18     }
 19 
 20     pub fn inputProduct(self: *const Job) choir.product.ProductKey {
 21         return choir.product.productKey(self.input.revision.metadata());
 22     }
 23 };
 24 
 25 const State = struct {
 26     ctx: choir.ir.Context,
 27     backend: Backend,
 28     module: choir.backends.x86_64.jit.ModuleHandle,
 29 
 30     fn deinit(ptr: *anyopaque, allocator: Allocator) void {
 31         const self: *State = @ptrCast(@alignCast(ptr));
 32         self.backend.deinit();
 33         self.ctx.deinit(allocator);
 34         allocator.destroy(self);
 35     }
 36 
 37     fn lookupExport(ptr: *anyopaque, name: []const u8) ?usize {
 38         const self: *State = @ptrCast(@alignCast(ptr));
 39         return self.backend.runtime.functionAddress(self.module, name) catch null;
 40     }
 41 };
 42 
 43 const vtable = composition.FragmentVTable{
 44     .deinit = State.deinit,
 45     .lookup_export = State.lookupExport,
 46 };
 47 
 48 pub fn compile(allocator: Allocator, program: *const source.Program) !Job {
 49     try program.verify();
 50     var ctx = try choir.ir.Context.init(allocator, choir.ir.Context.Limits.standard);
 51     defer ctx.deinit(allocator);
 52     try choir.dialects.registerAllDialects(&ctx);
 53     const module = try buildModule(&ctx, program);
 54     const input = try publication.capture(allocator, module, source.hostProductRef());
 55     errdefer input.release();
 56     var backend = try Backend.init(allocator, &ctx, .standard);
 57     defer backend.deinit();
 58     var artifact = try backend.compileFunctionToArtifact(module, source.host_symbol);
 59     errdefer artifact.deinit();
 60     try artifact.verification.replace(artifact.allocator, .{ .state = .passed, .stage = "choir.x86_64.machine_code" });
 61     return .{
 62         .artifact = artifact,
 63         .input = input,
 64     };
 65 }
 66 
 67 pub fn materializeProduct(
 68     product: *const Job,
 69     allocator: Allocator,
 70     _: *const composition.CompositionModule,
 71     fragment: *const composition.Fragment,
 72 ) !composition.MaterializedFragment {
 73     if (fragment.id.value != source.host_fragment.value or fragment.pipeline != .choir) return error.FragmentMismatch;
 74     if (!fragment.pipeline_input.eql(product.inputProduct())) return error.ProductMismatch;
 75     if (fragment.artifacts.len != 1 or !fragment.artifacts[0].eql(product.artifact)) return error.ArtifactMismatch;
 76     const export_value = fragment.exportByName("entry") orelse return error.MissingEntry;
 77     if (!std.mem.eql(u8, export_value.symbol, source.host_symbol) or export_value.abi_version != composition.abi.version) return error.MissingEntry;
 78     const runtime_import = fragment.importByName("invoke") orelse return error.MissingRuntime;
 79     if (!std.mem.eql(u8, runtime_import.symbol, composition.abi.invoke_symbol) or
 80         runtime_import.kind != .runtime or
 81         runtime_import.abi_version != composition.abi.version)
 82     {
 83         return error.MissingRuntime;
 84     }
 85 
 86     const state = try allocator.create(State);
 87     errdefer allocator.destroy(state);
 88     state.ctx = try choir.ir.Context.init(allocator, choir.ir.Context.Limits.standard);
 89     errdefer state.ctx.deinit(allocator);
 90     try choir.dialects.registerAllDialects(&state.ctx);
 91     state.backend = try Backend.init(allocator, &state.ctx, .standard);
 92     errdefer state.backend.deinit();
 93     try state.backend.runtime.registerExternalSymbol(composition.abi.invoke_symbol, @intFromPtr(&composition.loaded.invoke));
 94     state.module = try state.backend.runtime.loadMachineCodeArtifact(&fragment.artifacts[0]);
 95     return .{
 96         .state = state,
 97         .vtable = &vtable,
 98     };
 99 }
100 
101 pub fn materializer(product: *Job) composition.FragmentMaterializer {
102     return .{
103         .id = source.host_fragment,
104         .context = product,
105         .materialize = materialize,
106     };
107 }
108 
109 fn materialize(
110     ptr: *anyopaque,
111     allocator: Allocator,
112     module: *const composition.CompositionModule,
113     fragment: *const composition.Fragment,
114 ) anyerror!composition.MaterializedFragment {
115     const product: *Job = @ptrCast(@alignCast(ptr));
116     return try materializeProduct(product, allocator, module, fragment);
117 }
118 
119 fn buildModule(ctx: *choir.ir.Context, program: *const source.Program) !*choir.ir.Operation {
120     const ArithDialect = choir.dialects.ArithDialect;
121     const BuiltinDialect = choir.dialects.BuiltinDialect;
122     const FuncDialect = choir.dialects.FuncDialect;
123     const location = choir.ir.Location.getUnknown();
124     const i64_type = try ArithDialect.getScalarType(ctx, .i64);
125 
126     const module = try BuiltinDialect.ModuleOp.create(ctx, location);
127     const body = module.getBodyBlock();
128     const invoke_declaration = try FuncDialect.FuncOp.createDeclaration(
129         ctx,
130         location,
131         composition.abi.invoke_symbol,
132         &.{ i64_type, i64_type, i64_type },
133         &.{i64_type},
134     );
135     try body.addOperation(invoke_declaration.op);
136 
137     var entry = try FuncDialect.FuncOp.create(
138         ctx,
139         location,
140         source.host_symbol,
141         &.{ i64_type, i64_type },
142         &.{i64_type},
143     );
144     try body.addOperation(entry.op);
145 
146     const entry_block = entry.getEntryBlock();
147     var call_site = try ArithDialect.ConstantOp.createInt(
148         ctx,
149         location,
150         i64_type,
151         @intCast(program.add.call_site.value),
152     );
153     try entry_block.addOperation(call_site.op);
154     var call = try FuncDialect.CallOp.create(
155         ctx,
156         location,
157         composition.abi.invoke_symbol,
158         &.{ entry.getArgument(0), call_site.getResult(), entry.getArgument(1) },
159         &.{i64_type},
160     );
161     try entry_block.addOperation(call.op);
162     const return_op = try FuncDialect.ReturnOp.create(ctx, location, &.{call.getResult(0).?});
163     try entry_block.addOperation(return_op.op);
164     return module.op;
165 }