lib/choir/src/composition/loaded/dispatch.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const composition = @import("../root.zig");
3 const loaded = @import("root.zig");
4
5 const abi = composition.abi;
6 const model = composition.module;
7 const source = composition.source;
8
9 pub fn invoke(context_optional: ?*abi.Context, call_site_id: u64, frame_optional: ?*abi.Frame) callconv(.c) i64 {
10 const context = context_optional orelse {
11 if (frame_optional) |frame| frame.setStatusIfCompatible(.incompatible_context);
12 return statusCode(.incompatible_context);
13 };
14 const frame = frame_optional orelse return statusCode(.incompatible_frame);
15 const context_status = context.validate();
16 if (context_status != .ok) {
17 frame.setStatusIfCompatible(context_status);
18 return statusCode(context_status);
19 }
20 const owner: *loaded.LoadedComposition = @ptrFromInt(context.owner_address);
21 if (owner.context.owner_cookie != context.owner_cookie or owner.context.owner_address != context.owner_address) {
22 frame.setStatusIfCompatible(.incompatible_context);
23 return statusCode(.incompatible_context);
24 }
25 const status = dispatch(owner, call_site_id, frame);
26 frame.setStatusIfCompatible(status);
27 return statusCode(status);
28 }
29
30 pub fn dispatch(owner: *loaded.LoadedComposition, call_site_id: u64, frame: *abi.Frame) abi.Status {
31 if (owner.invocations.isClosing()) return .composition_closing;
32 const frame_status = frame.validate(&owner.context);
33 if (frame_status != .ok) return frame_status;
34 const selected_variant = owner.module.variant(owner.selected_variant) orelse return .unknown_call_site;
35 const call_site = selected_variant.callSite(.{ .value = call_site_id }) orelse return .unknown_call_site;
36 const resources_status = validateResources(owner, call_site, frame);
37 if (resources_status != .ok) return resources_status;
38 const fragment_value = owner.loadedFragment(call_site.callee) orelse return .missing_fragment;
39 const invoke_fn = fragment_value.vtable.invoke orelse return .missing_fragment;
40 return invoke_fn(fragment_value.state, call_site, &owner.context, frame);
41 }
42
43 fn validateResources(owner: *const loaded.LoadedComposition, call_site: *const model.CallSite, frame: *const abi.Frame) abi.Status {
44 const resolved = frame.resources(&owner.context);
45 const resources = switch (resolved) {
46 .status => |status| return status,
47 .value => |values| values,
48 };
49 const expected_count = std.math.add(usize, call_site.inputs.len, call_site.outputs.len) catch return .invalid_resource;
50 if (resources.len != expected_count or expected_count > abi.max_resources) return .invalid_resource;
51 for (call_site.inputs, 0..) |boundary_id, index| {
52 const boundary = owner.module.source_module.boundary(boundary_id) orelse return .invalid_resource;
53 if (!resourceMatches(resources[index], boundary)) return .invalid_resource;
54 }
55 for (call_site.outputs, 0..) |boundary_id, index| {
56 const boundary = owner.module.source_module.boundary(boundary_id) orelse return .invalid_resource;
57 if (!resourceMatches(resources[call_site.inputs.len + index], boundary)) return .invalid_resource;
58 }
59 return .ok;
60 }
61
62 fn statusCode(status: abi.Status) i64 {
63 return @intCast(@backingInt(status));
64 }
65
66 fn resourceMatches(resource: abi.Resource, boundary: *const source.Boundary) bool {
67 const access = resource.accessValue() orelse return false;
68 return resource.byte_size == boundary.byte_size and access == boundary.access;
69 }