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 }