lib/gpu/src/host.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const choir_abi = @import("choir_abi");
3
4 const backend = @import("root.zig");
5
6 pub const Binding = extern struct {
7 handle_id: u64,
8 access: u8,
9 ownership: u8,
10 reserved: [6]u8 = @splat(0),
11 byte_size: u64,
12 };
13
14 pub const Scalar = extern struct {
15 tag: u8,
16 reserved: [7]u8 = @splat(0),
17 bits: u64,
18 };
19
20 pub const ScalarTag = enum(u8) {
21 i32 = 0,
22 u32 = 1,
23 i64 = 2,
24 u64 = 3,
25 f32 = 4,
26 f64 = 5,
27 };
28
29 pub const Status = enum(i32) {
30 ok = 0,
31 invalid_artifact = 1,
32 invalid_buffer = 2,
33 launch_failed = 3,
34 out_of_memory = 4,
35 runtime_unavailable = 5,
36 };
37
38 pub fn binding(source: backend.BufferBinding) backend.BackendError!Binding {
39 const byte_size = std.math.cast(u64, source.byte_size) orelse return error.InvalidBuffer;
40 return .{
41 .handle_id = source.handle.id,
42 .access = @backingInt(source.access),
43 .ownership = @backingInt(source.ownership),
44 .byte_size = byte_size,
45 };
46 }
47
48 pub fn scalar(argument: choir_abi.ScalarArgument) Scalar {
49 return switch (argument) {
50 .i32 => |value| .{ .tag = @backingInt(ScalarTag.i32), .bits = @as(u32, @bitCast(value)) },
51 .u32 => |value| .{ .tag = @backingInt(ScalarTag.u32), .bits = value },
52 .i64 => |value| .{ .tag = @backingInt(ScalarTag.i64), .bits = @as(u64, @bitCast(value)) },
53 .u64 => |value| .{ .tag = @backingInt(ScalarTag.u64), .bits = value },
54 .f32 => |value| .{ .tag = @backingInt(ScalarTag.f32), .bits = @as(u32, @bitCast(value)) },
55 .f64 => |value| .{ .tag = @backingInt(ScalarTag.f64), .bits = @as(u64, @bitCast(value)) },
56 };
57 }
58
59 pub fn status(code: i32) backend.BackendError!void {
60 return switch (code) {
61 @backingInt(Status.ok) => {},
62 @backingInt(Status.invalid_artifact) => error.InvalidArtifact,
63 @backingInt(Status.invalid_buffer) => error.InvalidBuffer,
64 @backingInt(Status.launch_failed) => error.LaunchFailed,
65 @backingInt(Status.out_of_memory) => error.OutOfMemory,
66 @backingInt(Status.runtime_unavailable) => error.RuntimeUnavailable,
67 else => error.RuntimeUnavailable,
68 };
69 }
70
71 pub fn pointer(slice: anytype) usize {
72 return if (slice.len == 0) 0 else @intFromPtr(slice.ptr);
73 }
74
75 test "host binding narrows buffer bindings to the wire layout" {
76 const wire = try binding(.{
77 .handle = .{ .id = 7, .backend = .wasm, .byte_size = 64, .ownership = .backend },
78 .access = .read_write,
79 .ownership = .backend,
80 .byte_size = 64,
81 });
82 try std.testing.expectEqual(@as(u64, 7), wire.handle_id);
83 try std.testing.expectEqual(@as(u64, 64), wire.byte_size);
84 try std.testing.expectEqual(@backingInt(backend.BufferAccess.read_write), wire.access);
85 try std.testing.expectEqual(@as(usize, 24), @sizeOf(Binding));
86 }
87
88 test "host scalar preserves bit patterns per tag" {
89 const float_scalar = scalar(.{ .f32 = -1.5 });
90 try std.testing.expectEqual(@backingInt(ScalarTag.f32), float_scalar.tag);
91 try std.testing.expectEqual(@as(u64, @as(u32, @bitCast(@as(f32, -1.5)))), float_scalar.bits);
92 try std.testing.expectEqual(@as(usize, 16), @sizeOf(Scalar));
93 }
94
95 test "host status maps wire codes onto backend errors" {
96 try status(0);
97 try std.testing.expectError(error.InvalidArtifact, status(1));
98 try std.testing.expectError(error.InvalidBuffer, status(2));
99 try std.testing.expectError(error.LaunchFailed, status(3));
100 try std.testing.expectError(error.OutOfMemory, status(4));
101 try std.testing.expectError(error.RuntimeUnavailable, status(5));
102 try std.testing.expectError(error.RuntimeUnavailable, status(99));
103 }