[Phi] implementing fully async queues, fences and semaphores
Mirror Gitea refs to GitHub / mirror (push) Successful in 15s
Build / build (push) Failing after 1m1s
Test / build_and_test (push) Successful in 6m8s

This commit is contained in:
2026-08-20 14:11:12 +02:00
parent 35e6c1d099
commit 3e12e97fe2
16 changed files with 1075 additions and 101 deletions
+22 -22
View File
@@ -264,7 +264,7 @@ Assume thou that functions lacking in this array are, for now, not intended to b
| vkAllocateDescriptorSets | ⚙️ WIP |
| vkAllocateMemory | ✅ Implemented |
| vkBeginCommandBuffer | ✅ Implemented |
| vkBindBufferMemory | ⚙️ WIP |
| vkBindBufferMemory | ✅ Implemented |
| vkBindImageMemory | ✅ Implemented |
| vkCmdBeginQuery | ⚙️ WIP |
| vkCmdBeginRenderPass | ⚙️ WIP |
@@ -272,13 +272,13 @@ Assume thou that functions lacking in this array are, for now, not intended to b
| vkCmdBindIndexBuffer | ⚙️ WIP |
| vkCmdBindPipeline | ⚙️ WIP |
| vkCmdBindVertexBuffers | ⚙️ WIP |
| vkCmdBlitImage | ✅ Implemented |
| vkCmdBlitImage | ⚙️ WIP |
| vkCmdClearAttachments | ⚙️ WIP |
| vkCmdClearColorImage | ⚙️ WIP |
| vkCmdClearDepthStencilImage | ⚙️ WIP |
| vkCmdCopyBuffer | ✅ Implemented |
| vkCmdCopyBufferToImage | ✅ Implemented |
| vkCmdCopyImage | ⚙️ WIP |
| vkCmdCopyImage | ✅ Implemented |
| vkCmdCopyImageToBuffer | ✅ Implemented |
| vkCmdCopyQueryPoolResults | ⚙️ WIP |
| vkCmdDispatch | ⚙️ WIP |
@@ -312,7 +312,7 @@ Assume thou that functions lacking in this array are, for now, not intended to b
| vkCmdUpdateBuffer | ⚙️ WIP |
| vkCmdWaitEvents | ⚙️ WIP |
| vkCmdWriteTimestamp | ⚙️ WIP |
| vkCreateBuffer | ⚙️ WIP |
| vkCreateBuffer | ✅ Implemented |
| vkCreateBufferView | ⚙️ WIP |
| vkCreateCommandPool | ✅ Implemented |
| vkCreateComputePipelines | ⚙️ WIP |
@@ -320,7 +320,7 @@ Assume thou that functions lacking in this array are, for now, not intended to b
| vkCreateDescriptorSetLayout | ⚙️ WIP |
| vkCreateDevice | ✅ Implemented |
| vkCreateEvent | ⚙️ WIP |
| vkCreateFence | ⚙️ WIP |
| vkCreateFence | ✅ Implemented |
| vkCreateFramebuffer | ⚙️ WIP |
| vkCreateGraphicsPipelines | ⚙️ WIP |
| vkCreateImage | ✅ Implemented |
@@ -331,21 +331,21 @@ Assume thou that functions lacking in this array are, for now, not intended to b
| vkCreateQueryPool | ⚙️ WIP |
| vkCreateRenderPass | ⚙️ WIP |
| vkCreateSampler | ⚙️ WIP |
| vkCreateSemaphore | ⚙️ WIP |
| vkCreateSemaphore | ✅ Implemented |
| vkCreateShaderModule | ⚙️ WIP |
| vkCreateSwapchainKHR | ⚙️ WIP |
| vkCreateWaylandSurfaceKHR | ⚙️ WIP |
| vkCreateWin32SurfaceKHR | ⚙️ WIP |
| vkCreateXcbSurfaceKHR | ⚙️ WIP |
| vkCreateXlibSurfaceKHR | ⚙️ WIP |
| vkDestroyBuffer | ⚙️ WIP |
| vkDestroyBuffer | ✅ Implemented |
| vkDestroyBufferView | ⚙️ WIP |
| vkDestroyCommandPool | ✅ Implemented |
| vkDestroyDescriptorPool | ⚙️ WIP |
| vkDestroyDescriptorSetLayout | ⚙️ WIP |
| vkDestroyDevice | ✅ Implemented |
| vkDestroyEvent | ⚙️ WIP |
| vkDestroyFence | ⚙️ WIP |
| vkDestroyFence | ✅ Implemented |
| vkDestroyFramebuffer | ⚙️ WIP |
| vkDestroyImage | ✅ Implemented |
| vkDestroyImageView | ⚙️ WIP |
@@ -356,11 +356,11 @@ Assume thou that functions lacking in this array are, for now, not intended to b
| vkDestroyQueryPool | ⚙️ WIP |
| vkDestroyRenderPass | ⚙️ WIP |
| vkDestroySampler | ⚙️ WIP |
| vkDestroySemaphore | ⚙️ WIP |
| vkDestroySemaphore | ✅ Implemented |
| vkDestroyShaderModule | ⚙️ WIP |
| vkDestroySurfaceKHR | ⚙️ WIP |
| vkDestroySwapchainKHR | ⚙️ WIP |
| vkDeviceWaitIdle | ⚙️ WIP |
| vkDeviceWaitIdle | ✅ Implemented |
| vkEndCommandBuffer | ✅ Implemented |
| vkEnumerateDeviceExtensionProperties | ⚙️ WIP |
| vkEnumerateDeviceLayerProperties | ⚙️ WIP |
@@ -369,7 +369,7 @@ Assume thou that functions lacking in this array are, for now, not intended to b
| vkEnumeratePhysicalDeviceGroupsKHR | ⚙️ WIP |
| vkEnumeratePhysicalDevices | ✅ Implemented |
| vkFlushMappedMemoryRanges | ⚙️ WIP |
| vkFreeCommandBuffers | ⚙️ WIP |
| vkFreeCommandBuffers | ✅ Implemented |
| vkFreeDescriptorSets | ⚙️ WIP |
| vkFreeMemory | ✅ Implemented |
| vkGetBufferDeviceAddress | ⚙️ WIP |
@@ -380,14 +380,14 @@ Assume thou that functions lacking in this array are, for now, not intended to b
| vkGetDeviceGroupPresentCapabilitiesKHR | ⚙️ WIP |
| vkGetDeviceGroupSurfacePresentModesKHR | ⚙️ WIP |
| vkGetDeviceMemoryCommitment | ⚙️ WIP |
| vkGetDeviceProcAddr | ⚙️ WIP |
| vkGetDeviceQueue | ⚙️ WIP |
| vkGetEventStatus | ⚙️ WIP |
| vkGetFenceStatus | ⚙️ WIP |
| vkGetDeviceProcAddr | ✅ Implemented |
| vkGetDeviceQueue | ✅ Implemented |
| vkGetEventStatus | ✅ Implemented |
| vkGetFenceStatus | ✅ Implemented |
| vkGetImageMemoryRequirements | ✅ Implemented |
| vkGetImageSparseMemoryRequirements | ⚙️ WIP |
| vkGetImageSubresourceLayout | ✅ Implemented |
| vkGetInstanceProcAddr | ⚙️ WIP |
| vkGetInstanceProcAddr | ✅ Implemented |
| vkGetPhysicalDeviceFeatures | ✅ Implemented |
| vkGetPhysicalDeviceFormatProperties | ⚙️ WIP |
| vkGetPhysicalDeviceImageFormatProperties | ✅ Implemented |
@@ -408,22 +408,22 @@ Assume thou that functions lacking in this array are, for now, not intended to b
| vkGetRenderAreaGranularity | ⚙️ WIP |
| vkGetSwapchainImagesKHR | ⚙️ WIP |
| vkInvalidateMappedMemoryRanges | ⚙️ WIP |
| vkMapMemory | ⚙️ WIP |
| vkMapMemory | ✅ Implemented |
| vkMergePipelineCaches | ⚙️ WIP |
| vkQueueBindSparse | ⚙️ WIP |
| vkQueuePresentKHR | ⚙️ WIP |
| vkQueueSubmit | ✅ Implemented |
| vkQueueWaitIdle | ⚙️ WIP |
| vkResetCommandBuffer | ⚙️ WIP |
| vkQueueWaitIdle | ✅ Implemented |
| vkResetCommandBuffer | ✅ Implemented |
| vkResetCommandPool | ✅ Implemented |
| vkResetDescriptorPool | ⚙️ WIP |
| vkResetEvent | ⚙️ WIP |
| vkResetFences | ⚙️ WIP |
| vkResetFences | ✅ Implemented |
| vkResetQueryPool | ⚙️ WIP |
| vkSetEvent | ⚙️ WIP |
| vkUnmapMemory | ⚙️ WIP |
| vkUnmapMemory | ✅ Implemented |
| vkUpdateDescriptorSets | ⚙️ WIP |
| vkWaitForFences | ⚙️ WIP |
| vkWaitForFences | ✅ Implemented |
</details>
+2 -1
View File
@@ -595,7 +595,7 @@ fn addPhiDaemonCompilerArgs(
switch (optimize) {
.Debug => cmd.addArgs(&.{ "-O0", "-g3" }),
.ReleaseSafe => cmd.addArgs(&.{ "-O2", "-g", "-DNDEBUG" }),
.ReleaseFast => cmd.addArgs(&.{ "-O3", "-DNDEBUG" }),
.ReleaseFast => cmd.addArgs(&.{ "-O3", "-DNDEBUG", "-DNOLOGS" }),
.ReleaseSmall => cmd.addArgs(&.{ "-Os", "-DNDEBUG" }),
}
}
@@ -612,6 +612,7 @@ fn addPhiDaemon(b: *std.Build, optimize: std.builtin.OptimizeMode, cc: []const u
"src/phi/mic/Image.c",
"src/phi/mic/Logger.c",
"src/phi/mic/Memory.c",
"src/phi/mic/Queue.c",
"src/phi/mic/Transport.c",
// Add non-AVX files here
};
+39 -2
View File
@@ -8,6 +8,10 @@ const Self = @This();
pub const Interface = base.BinarySemaphore;
interface: Interface,
mutex: std.Io.Mutex,
condition: std.Io.Condition,
is_signaled: bool,
is_failed: bool,
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.SemaphoreCreateInfo) VkError!*Self {
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
@@ -23,6 +27,10 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
self.* = .{
.interface = interface,
.mutex = .init,
.condition = .init,
.is_signaled = false,
.is_failed = false,
};
return self;
}
@@ -34,10 +42,39 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
pub fn signal(interface: *Interface) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self;
const io = interface.owner.io();
self.mutex.lock(io) catch return VkError.DeviceLost;
defer self.mutex.unlock(io);
if (self.is_failed) return VkError.DeviceLost;
self.is_signaled = true;
self.condition.broadcast(io);
}
/// Latch an asynchronous queue/device failure and wake all host waiters
pub fn fail(interface: *Interface) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const io = interface.owner.io();
self.mutex.lock(io) catch return;
defer self.mutex.unlock(io);
self.is_failed = true;
self.condition.broadcast(io);
}
pub fn wait(interface: *Interface) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self;
const io = interface.owner.io();
self.mutex.lock(io) catch return VkError.DeviceLost;
defer self.mutex.unlock(io);
while (!self.is_signaled and !self.is_failed) {
self.condition.wait(io, &self.mutex) catch return VkError.DeviceLost;
}
if (self.is_failed) return VkError.DeviceLost;
self.is_signaled = false;
}
+76 -5
View File
@@ -9,6 +9,10 @@ const Self = @This();
pub const Interface = base.Fence;
interface: Interface,
mutex: std.Io.Mutex,
condition: std.Io.Condition,
is_signaled: bool,
is_failed: bool,
pub fn create(device: *Device, allocator: std.mem.Allocator, info: *const vk.FenceCreateInfo) VkError!*Self {
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
@@ -26,6 +30,10 @@ pub fn create(device: *Device, allocator: std.mem.Allocator, info: *const vk.Fen
self.* = .{
.interface = interface,
.mutex = .init,
.condition = .init,
.is_signaled = info.flags.signaled_bit,
.is_failed = false,
};
return self;
}
@@ -37,21 +45,84 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
pub fn getStatus(interface: *Interface) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self;
const io = interface.owner.io();
self.mutex.lock(io) catch return VkError.DeviceLost;
defer self.mutex.unlock(io);
if (self.is_failed) return VkError.DeviceLost;
if (!self.is_signaled) return VkError.NotReady;
}
pub fn reset(interface: *Interface) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self;
const io = interface.owner.io();
self.mutex.lock(io) catch return VkError.DeviceLost;
defer self.mutex.unlock(io);
if (self.is_failed) return VkError.DeviceLost;
self.is_signaled = false;
}
pub fn signal(interface: *Interface) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self;
const io = interface.owner.io();
self.mutex.lock(io) catch return VkError.DeviceLost;
defer self.mutex.unlock(io);
if (self.is_failed) return VkError.DeviceLost;
self.is_signaled = true;
self.condition.broadcast(io);
}
/// Latch an asynchronous queue/device failure and wake all host waiters
pub fn fail(interface: *Interface) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const io = interface.owner.io();
self.mutex.lock(io) catch return;
defer self.mutex.unlock(io);
self.is_failed = true;
self.condition.broadcast(io);
}
pub fn wait(interface: *Interface, timeout: u64) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self;
_ = timeout;
const io = interface.owner.io();
if (timeout == std.math.maxInt(@TypeOf(timeout))) {
self.mutex.lock(io) catch return VkError.DeviceLost;
defer self.mutex.unlock(io);
while (!self.is_signaled and !self.is_failed) {
self.condition.wait(io, &self.mutex) catch return VkError.DeviceLost;
}
if (self.is_failed) return VkError.DeviceLost;
return;
}
const deadline = std.Io.Clock.Timestamp.fromNow(io, .{
.raw = .fromNanoseconds(@intCast(timeout)),
.clock = .awake,
});
while (true) {
{
self.mutex.lock(io) catch return VkError.DeviceLost;
defer self.mutex.unlock(io);
if (self.is_failed) return VkError.DeviceLost;
if (self.is_signaled) return;
}
const remaining = deadline.durationFromNow(io);
if (remaining.raw.nanoseconds <= 0) return VkError.Timeout;
(std.Io.Clock.Duration{
.raw = .fromNanoseconds(@min(remaining.raw.nanoseconds, std.time.ns_per_ms)),
.clock = .awake,
}).sleep(io) catch return VkError.DeviceLost;
}
}
+613 -33
View File
@@ -4,8 +4,10 @@ const base = @import("base");
const lib = @import("lib.zig");
const proto = lib.proto;
const PhiBinarySemaphore = @import("PhiBinarySemaphore.zig");
const PhiCommandBuffer = @import("PhiCommandBuffer.zig");
const PhiDevice = @import("PhiDevice.zig");
const PhiFence = @import("PhiFence.zig");
const PhiTransport = @import("PhiTransport.zig");
const VkError = base.VkError;
@@ -13,7 +15,55 @@ const VkError = base.VkError;
const Self = @This();
pub const Interface = base.Queue;
const ring_capacity: usize = @intCast(proto.PHI_QUEUE_RING_CAPACITY);
const ring_capacity_u64: u64 = @intCast(ring_capacity);
const shutdown_sequence = std.math.maxInt(u64);
const PreparedSubmit = struct {
wait_semaphores: std.ArrayList(*base.BinarySemaphore),
signal_semaphores: std.ArrayList(*base.BinarySemaphore),
command_backing: ?[]u8,
scif_offset: ?u64,
registered_size: usize,
command_size: usize,
command_count: u64,
};
const PendingCompletion = struct {
signal_semaphores: std.ArrayList(*base.BinarySemaphore),
fence: ?*base.Fence,
command_backing: ?[]u8,
scif_offset: ?u64,
registered_size: usize,
};
const TaskData = struct {
queue: *Self,
sequence: usize,
submits: std.ArrayList(PreparedSubmit),
fence: ?*base.Fence,
};
interface: Interface,
transport: PhiTransport,
ring_backing: []u8,
ring_offset: u64,
shared: *proto.PhiQueueShared,
submit_group: std.Io.Group,
completion_group: std.Io.Group,
mutex: std.Io.Mutex,
condition: std.Io.Condition,
next_task_sequence: usize,
executing_task_sequence: usize,
next_remote_sequence: u64,
completed_sequence: u64,
pending: [ring_capacity]?PendingCompletion,
error_state: ?VkError,
shutting_down: bool,
remote_stopped: bool,
pub fn create(allocator: std.mem.Allocator, device: *base.Device, index: u32, family_index: u32, flags: vk.DeviceQueueCreateFlags) VkError!*Interface {
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
@@ -26,12 +76,118 @@ pub fn create(allocator: std.mem.Allocator, device: *base.Device, index: u32, fa
.waitIdle = waitIdle,
};
self.* = .{ .interface = interface };
const phi_device: *PhiDevice = @alignCast(@fieldParentPtr("interface", device));
var transport = try phi_device.transport.connectPeer();
errdefer transport.close();
const device_allocator = device.device_allocator.allocator();
const page_size = std.heap.pageSize();
const registered_size = std.mem.alignForward(usize, @sizeOf(proto.PhiQueueShared), page_size);
const ring_backing = device_allocator.alignedAlloc(
u8,
.fromByteUnits(std.heap.page_size_max),
registered_size,
) catch return VkError.OutOfHostMemory;
errdefer device_allocator.free(ring_backing);
@memset(ring_backing, 0);
const ring_offset = try transport.registerHostMemory(ring_backing);
errdefer transport.unregisterHostMemory(ring_offset, ring_backing.len) catch {};
const setup_request: proto.PhiQueueSetupRequest = .{
.scif_offset = ring_offset,
.scif_size = ring_backing.len,
.ring_capacity = @intCast(ring_capacity),
.reserved = 0,
};
var setup_reply = std.mem.zeroes(proto.PhiResultReply);
try transport.request(
proto.PHI_PACKET_QUEUE_SETUP,
std.mem.asBytes(&setup_request),
std.mem.asBytes(&setup_reply),
);
if (setup_reply.result.status != proto.PHI_STATUS_OK) {
return PhiTransport.statusToErr(setup_reply.result.status);
}
const shared: *proto.PhiQueueShared = @ptrCast(@alignCast(ring_backing.ptr));
self.* = .{
.interface = interface,
.transport = transport,
.ring_backing = ring_backing,
.ring_offset = ring_offset,
.shared = shared,
.submit_group = .init,
.completion_group = .init,
.mutex = .init,
.condition = .init,
.next_task_sequence = 0,
.executing_task_sequence = 0,
.next_remote_sequence = 1,
.completed_sequence = 0,
.pending = [_]?PendingCompletion{null} ** ring_capacity,
.error_state = null,
.shutting_down = false,
.remote_stopped = false,
};
self.completion_group.async(device.io(), completionRunner, .{self});
return &self.interface;
}
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const io = interface.owner.io();
const device_allocator = interface.owner.device_allocator.allocator();
waitIdle(interface) catch |err| {
std.log.scoped(.PhiQueue).warn("Queue did not become idle during destruction: {s}", .{@errorName(err)});
};
var graceful_shutdown = true;
self.mutex.lock(io) catch {
graceful_shutdown = false;
};
if (graceful_shutdown) {
self.shutting_down = true;
self.condition.broadcast(io);
self.mutex.unlock(io);
self.transport.sendQueueDoorbell(shutdown_sequence) catch |err| {
graceful_shutdown = false;
std.log.scoped(.PhiQueue).warn("Failed to send queue shutdown doorbell: {s}", .{@errorName(err)});
self.transport.close();
};
} else {
// Wake the blocking completion receiver before releasing queue storage
self.transport.close();
}
self.completion_group.await(io) catch |err| {
graceful_shutdown = false;
std.log.scoped(.PhiQueue).warn("Failed while joining completion receiver: {s}", .{@errorName(err)});
};
if (graceful_shutdown) {
self.mutex.lock(io) catch {
graceful_shutdown = false;
};
if (graceful_shutdown) {
graceful_shutdown = self.remote_stopped;
self.mutex.unlock(io);
}
}
if (graceful_shutdown) {
self.transport.unregisterHostMemory(self.ring_offset, self.ring_backing.len) catch |err| {
std.log.scoped(.PhiQueue).warn("Failed to unregister queue ring: {s}", .{@errorName(err)});
};
}
self.transport.close();
cleanupPendingAfterClose(self, device_allocator);
device_allocator.free(self.ring_backing);
allocator.destroy(self);
}
@@ -43,46 +199,470 @@ pub fn bindSparse(interface: *Interface, info: []const vk.BindSparseInfo, fence:
}
pub fn submit(interface: *Interface, infos: []Interface.SubmitInfo, fence: ?*base.Fence) VkError!void {
const device: *PhiDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const io = interface.owner.io();
const allocator = interface.owner.device_allocator.allocator();
for (infos) |info| {
for (info.wait_semaphores.items) |semaphore| {
try semaphore.wait();
}
try self.checkHealthy();
for (info.command_buffers.items) |command_buffer| {
const phi_command_buffer: *PhiCommandBuffer = @alignCast(@fieldParentPtr("interface", command_buffer));
const data = allocator.create(TaskData) catch return VkError.OutOfDeviceMemory;
errdefer allocator.destroy(data);
const work_execution_request: proto.PhiWorkExecutionRequest = .{
.cmd_count = phi_command_buffer.serialized_cmd_count,
.command_buffer_size = phi_command_buffer.commands.items.len,
};
const payload_size = @sizeOf(proto.PhiWorkExecutionRequest) + phi_command_buffer.commands.items.len;
const allocator = interface.host_allocator.allocator();
const payload = allocator.alloc(u8, payload_size) catch return VkError.OutOfHostMemory;
defer allocator.free(payload);
var prepared_submits = try prepareSubmits(self, allocator, infos);
errdefer deinitPreparedSubmits(self, allocator, &prepared_submits);
@memcpy(payload[0..@sizeOf(proto.PhiWorkExecutionRequest)], std.mem.asBytes(&work_execution_request));
@memcpy(payload[@sizeOf(proto.PhiWorkExecutionRequest)..], phi_command_buffer.commands.items);
const sequence = blk: {
self.mutex.lock(io) catch return VkError.DeviceLost;
defer self.mutex.unlock(io);
// Synchronous queues for now
var reply = std.mem.zeroes(proto.PhiResultReply);
try device.transport.request(proto.PHI_PACKET_WORK_EXECUTION, payload, std.mem.asBytes(&reply));
if (self.error_state) |err| return err;
if (self.shutting_down) return VkError.DeviceLost;
if (reply.result.status != proto.PHI_STATUS_OK) {
return PhiTransport.statusToErr(reply.result.status);
}
}
const value = self.next_task_sequence;
self.next_task_sequence += 1;
break :blk value;
};
for (info.signal_semaphores.items) |semaphore| {
try semaphore.signal();
}
}
if (fence) |value| {
try value.signal();
}
data.* = .{
.queue = self,
.sequence = sequence,
.submits = prepared_submits,
.fence = fence,
};
self.submit_group.async(io, taskRunner, .{data});
}
pub fn waitIdle(interface: *Interface) VkError!void {
_ = interface;
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const io = interface.owner.io();
self.submit_group.await(io) catch {
self.markLost(VkError.DeviceLost);
return VkError.DeviceLost;
};
self.mutex.lock(io) catch return VkError.DeviceLost;
defer self.mutex.unlock(io);
while (self.completed_sequence + 1 < self.next_remote_sequence and self.error_state == null) {
self.condition.wait(io, &self.mutex) catch return VkError.DeviceLost;
}
if (self.error_state) |err| return err;
}
fn checkHealthy(self: *Self) VkError!void {
const io = self.interface.owner.io();
self.mutex.lock(io) catch return VkError.DeviceLost;
defer self.mutex.unlock(io);
if (self.error_state) |err| return err;
if (self.shutting_down) return VkError.DeviceLost;
}
fn prepareSubmits(self: *Self, allocator: std.mem.Allocator, infos: []Interface.SubmitInfo) VkError!std.ArrayList(PreparedSubmit) {
var submits = std.ArrayList(PreparedSubmit).initCapacity(allocator, infos.len) catch return VkError.OutOfDeviceMemory;
errdefer deinitPreparedSubmits(self, allocator, &submits);
for (infos) |info| {
var prepared = try prepareSubmit(self, allocator, info);
submits.append(allocator, prepared) catch {
deinitPreparedSubmit(self, allocator, &prepared);
return VkError.OutOfDeviceMemory;
};
}
return submits;
}
fn prepareSubmit(self: *Self, allocator: std.mem.Allocator, info: Interface.SubmitInfo) VkError!PreparedSubmit {
var wait_semaphores = info.wait_semaphores.clone(allocator) catch return VkError.OutOfDeviceMemory;
errdefer wait_semaphores.deinit(allocator);
var signal_semaphores = info.signal_semaphores.clone(allocator) catch return VkError.OutOfDeviceMemory;
errdefer signal_semaphores.deinit(allocator);
var command_size: usize = 0;
var command_count: u64 = 0;
for (info.command_buffers.items) |command_buffer| {
const phi_command_buffer: *PhiCommandBuffer = @alignCast(@fieldParentPtr("interface", command_buffer));
if (phi_command_buffer.commands.items.len > std.math.maxInt(usize) - command_size) {
return VkError.OutOfHostMemory;
}
command_size += phi_command_buffer.commands.items.len;
const serialized_cmd_count: u64 = @intCast(phi_command_buffer.serialized_cmd_count);
if (serialized_cmd_count > std.math.maxInt(u64) - command_count) {
return VkError.OutOfHostMemory;
}
command_count += serialized_cmd_count;
}
var command_backing: ?[]u8 = null;
var scif_offset: ?u64 = null;
var registered_size: usize = 0;
if (command_size != 0) {
const page_size = std.heap.pageSize();
if (command_size > std.math.maxInt(usize) - (page_size - 1)) {
return VkError.OutOfHostMemory;
}
registered_size = std.mem.alignForward(usize, command_size, page_size);
const backing = allocator.alignedAlloc(
u8,
.fromByteUnits(std.heap.page_size_max),
registered_size,
) catch return VkError.OutOfHostMemory;
errdefer allocator.free(backing);
@memset(backing, 0);
var write_offset: usize = 0;
for (info.command_buffers.items) |command_buffer| {
const phi_command_buffer: *PhiCommandBuffer = @alignCast(@fieldParentPtr("interface", command_buffer));
const commands = phi_command_buffer.commands.items;
@memcpy(backing[write_offset .. write_offset + commands.len], commands);
write_offset += commands.len;
}
const offset = try self.transport.registerHostMemory(backing);
errdefer self.transport.unregisterHostMemory(offset, backing.len) catch {};
command_backing = backing;
scif_offset = offset;
}
return .{
.wait_semaphores = wait_semaphores,
.signal_semaphores = signal_semaphores,
.command_backing = command_backing,
.scif_offset = scif_offset,
.registered_size = registered_size,
.command_size = command_size,
.command_count = command_count,
};
}
fn deinitPreparedSubmits(self: *Self, allocator: std.mem.Allocator, submits: *std.ArrayList(PreparedSubmit)) void {
for (submits.items) |*prepared| {
deinitPreparedSubmit(self, allocator, prepared);
}
submits.deinit(allocator);
}
fn deinitPreparedSubmit(self: *Self, allocator: std.mem.Allocator, prepared: *PreparedSubmit) void {
prepared.wait_semaphores.deinit(allocator);
prepared.signal_semaphores.deinit(allocator);
if (prepared.scif_offset) |offset| {
self.transport.unregisterHostMemory(offset, prepared.registered_size) catch |err| {
std.log.scoped(.PhiQueue).warn("Failed to unregister staged command buffer: {s}", .{@errorName(err)});
};
}
if (prepared.command_backing) |backing| allocator.free(backing);
prepared.command_backing = null;
prepared.scif_offset = null;
prepared.registered_size = 0;
prepared.command_size = 0;
prepared.command_count = 0;
}
fn taskRunner(data: *TaskData) void {
const self = data.queue;
const io = self.interface.owner.io();
const allocator = self.interface.owner.device_allocator.allocator();
defer {
deinitPreparedSubmits(self, allocator, &data.submits);
allocator.destroy(data);
}
self.mutex.lock(io) catch {
failTask(data);
self.markLost(VkError.DeviceLost);
return;
};
while (data.sequence != self.executing_task_sequence and self.error_state == null) {
self.condition.wait(io, &self.mutex) catch {
self.mutex.unlock(io);
failTask(data);
self.markLost(VkError.DeviceLost);
return;
};
}
if (self.error_state != null) {
self.mutex.unlock(io);
failTask(data);
return;
}
self.mutex.unlock(io);
var task_error: ?VkError = null;
if (data.submits.items.len == 0) {
if (data.fence) |fence| {
var marker: PreparedSubmit = .{
.wait_semaphores = .empty,
.signal_semaphores = .empty,
.command_backing = null,
.scif_offset = null,
.registered_size = 0,
.command_size = 0,
.command_count = 0,
};
self.publish(&marker, fence) catch |err| {
task_error = err;
};
if (task_error == null) data.fence = null;
}
} else {
for (data.submits.items, 0..) |*prepared, info_index| {
for (prepared.wait_semaphores.items) |semaphore| {
semaphore.wait() catch |err| {
task_error = err;
break;
};
}
if (task_error != null) break;
const submission_fence = if (info_index + 1 == data.submits.items.len) data.fence else null;
self.publish(prepared, submission_fence) catch |err| {
task_error = err;
break;
};
if (submission_fence != null) data.fence = null;
}
}
if (task_error) |err| {
failTask(data);
self.markLost(err);
return;
}
self.mutex.lock(io) catch {
self.markLost(VkError.DeviceLost);
return;
};
self.executing_task_sequence += 1;
self.condition.broadcast(io);
self.mutex.unlock(io);
}
fn publish(self: *Self, prepared: *PreparedSubmit, fence: ?*base.Fence) VkError!void {
const io = self.interface.owner.io();
self.mutex.lock(io) catch return VkError.DeviceLost;
while ((self.next_remote_sequence - 1) - self.completed_sequence >= ring_capacity_u64 and self.error_state == null) {
self.condition.wait(io, &self.mutex) catch {
self.mutex.unlock(io);
return VkError.DeviceLost;
};
}
if (self.error_state) |err| {
self.mutex.unlock(io);
return err;
}
if (self.shutting_down) {
self.mutex.unlock(io);
return VkError.DeviceLost;
}
const sequence = self.next_remote_sequence;
if (sequence == shutdown_sequence) {
self.mutex.unlock(io);
return VkError.DeviceLost;
}
const slot: usize = @intCast((sequence - 1) % ring_capacity_u64);
if (self.pending[slot] != null) {
self.mutex.unlock(io);
return VkError.DeviceLost;
}
const command_scif_offset = prepared.scif_offset orelse 0;
self.pending[slot] = .{
.signal_semaphores = prepared.signal_semaphores,
.fence = fence,
.command_backing = prepared.command_backing,
.scif_offset = prepared.scif_offset,
.registered_size = prepared.registered_size,
};
prepared.signal_semaphores = .empty;
prepared.command_backing = null;
prepared.scif_offset = null;
prepared.registered_size = 0;
self.shared.submissions[slot] = .{
.sequence = sequence,
.command_scif_offset = command_scif_offset,
.command_size = prepared.command_size,
.command_count = prepared.command_count,
};
@atomicStore(
@TypeOf(self.shared.producer_sequence),
&self.shared.producer_sequence,
@intCast(sequence),
.release,
);
self.next_remote_sequence += 1;
self.mutex.unlock(io);
self.transport.sendQueueDoorbell(sequence) catch |err| {
self.markLost(VkError.DeviceLost);
return err;
};
}
fn completionRunner(self: *Self) void {
while (true) {
const completion = self.transport.receiveQueueCompletion() catch {
if (!self.isShuttingDown()) self.markLost(VkError.DeviceLost);
return;
};
if (completion.sequence == shutdown_sequence) {
const io = self.interface.owner.io();
self.mutex.lock(io) catch return;
self.remote_stopped = completion.status == proto.PHI_STATUS_OK;
self.condition.broadcast(io);
self.mutex.unlock(io);
return;
}
self.completeOne(completion);
}
}
fn completeOne(self: *Self, completion: proto.PhiQueueCompletion) void {
const io = self.interface.owner.io();
const allocator = self.interface.owner.device_allocator.allocator();
self.mutex.lock(io) catch {
self.markLost(VkError.DeviceLost);
return;
};
if (completion.sequence != self.completed_sequence + 1 or completion.sequence >= self.next_remote_sequence) {
self.mutex.unlock(io);
self.markLost(VkError.DeviceLost);
return;
}
const slot: usize = @intCast((completion.sequence - 1) % ring_capacity_u64);
var pending = self.pending[slot] orelse {
self.mutex.unlock(io);
self.markLost(VkError.DeviceLost);
return;
};
self.pending[slot] = null;
self.mutex.unlock(io);
var cleanup_failed = false;
if (pending.scif_offset) |offset| {
self.transport.unregisterHostMemory(offset, pending.registered_size) catch |err| {
cleanup_failed = true;
std.log.scoped(.PhiQueue).err("Failed to unregister completed command buffer: {s}", .{@errorName(err)});
};
}
if (pending.command_backing) |backing| allocator.free(backing);
if (completion.status != proto.PHI_STATUS_OK or cleanup_failed) {
self.markLost(VkError.DeviceLost);
failPending(&pending);
} else if (self.hasError()) {
failPending(&pending);
} else {
signalPending(self, &pending);
}
pending.signal_semaphores.deinit(allocator);
self.mutex.lock(io) catch {
self.markLost(VkError.DeviceLost);
return;
};
self.completed_sequence = completion.sequence;
self.condition.broadcast(io);
self.mutex.unlock(io);
}
fn signalPending(self: *Self, pending: *PendingCompletion) void {
var signal_failed = false;
for (pending.signal_semaphores.items) |semaphore| {
semaphore.signal() catch {
signal_failed = true;
};
}
if (pending.fence) |fence| {
fence.signal() catch {
signal_failed = true;
};
}
if (signal_failed) self.markLost(VkError.DeviceLost);
}
fn failPending(pending: *PendingCompletion) void {
for (pending.signal_semaphores.items) |semaphore| {
PhiBinarySemaphore.fail(semaphore);
}
if (pending.fence) |fence| PhiFence.fail(fence);
}
fn failTask(data: *TaskData) void {
for (data.submits.items) |*prepared| {
for (prepared.signal_semaphores.items) |semaphore| {
PhiBinarySemaphore.fail(semaphore);
}
}
if (data.fence) |fence| PhiFence.fail(fence);
}
fn markLost(self: *Self, _: VkError) void {
const io = self.interface.owner.io();
self.mutex.lock(io) catch return;
defer self.mutex.unlock(io);
const first_failure = self.error_state == null;
self.error_state = VkError.DeviceLost;
self.condition.broadcast(io);
if (!first_failure) return;
for (&self.pending) |*entry| {
if (entry.*) |*pending| failPending(pending);
}
}
fn hasError(self: *Self) bool {
const io = self.interface.owner.io();
self.mutex.lock(io) catch return true;
defer self.mutex.unlock(io);
return self.error_state != null;
}
fn isShuttingDown(self: *Self) bool {
const io = self.interface.owner.io();
self.mutex.lock(io) catch return true;
defer self.mutex.unlock(io);
return self.shutting_down;
}
fn cleanupPendingAfterClose(self: *Self, allocator: std.mem.Allocator) void {
for (&self.pending) |*entry| {
if (entry.*) |*pending| {
failPending(pending);
if (pending.command_backing) |backing| allocator.free(backing);
pending.signal_semaphores.deinit(allocator);
entry.* = null;
}
}
}
+33 -4
View File
@@ -13,6 +13,7 @@ epd: Endpoint,
sequence: u64 = 1,
mutex: std.Io.Mutex = .init,
instance: *base.Instance,
node_id: u16,
pub fn init(instance: *base.Instance, node_id: u16) VkError!Self {
const epd = blk: {
@@ -45,6 +46,7 @@ pub fn init(instance: *base.Instance, node_id: u16) VkError!Self {
var self: Self = .{
.epd = epd,
.instance = instance,
.node_id = node_id,
};
try self.handshake();
@@ -52,17 +54,30 @@ pub fn init(instance: *base.Instance, node_id: u16) VkError!Self {
return self;
}
pub fn connectPeer(self: *const Self) VkError!Self {
return init(self.instance, self.node_id);
}
pub fn deinit(self: *Self) void {
var reply: proto.PhiResult = undefined;
self.request(proto.PHI_PACKET_SHUTDOWN, &.{}, std.mem.asBytes(&reply)) catch |err| {
std.log.scoped(.PhiTransport).warn("Failed to shut down remote session: {s}", .{@errorName(err)});
};
closeEndpoint(self.epd);
scif.unload();
self.close();
std.log.scoped(.PhiTransport).info("Closed connection", .{});
}
/// Close a transport without issuing an RPC shutdown. Queue transports switch
/// to a raw full-duplex doorbell protocol after setup and must use this path
pub fn close(self: *Self) void {
if (self.epd < 0) return;
closeEndpoint(self.epd);
self.epd = -1;
scif.unload();
}
pub fn request(self: *Self, command: c_uint, payload: []const u8, reply_payload: []u8) VkError!void {
self.mutex.lock(self.instance.io()) catch return VkError.DeviceLost;
defer self.mutex.unlock(self.instance.io());
@@ -98,6 +113,20 @@ pub fn request(self: *Self, command: c_uint, payload: []const u8, reply_payload:
try self.readAll(reply_payload);
}
pub fn sendQueueDoorbell(self: *Self, sequence: u64) VkError!void {
const doorbell: proto.PhiQueueDoorbell = .{
.sequence = sequence,
};
try self.writeAll(std.mem.asBytes(&doorbell));
}
pub fn receiveQueueCompletion(self: *Self) VkError!proto.PhiQueueCompletion {
// SAFETY: readAll initializes the complete structure.
var completion: proto.PhiQueueCompletion = undefined;
try self.readAll(std.mem.asBytes(&completion));
return completion;
}
pub fn statusToErr(status: c_int) VkError {
return switch (status) {
proto.PHI_STATUS_OUT_OF_MEMORY => VkError.OutOfDeviceMemory,
@@ -112,7 +141,7 @@ fn writeAll(self: *Self, bytes: []const u8) VkError!void {
while (offset < bytes.len) {
const written = scif.send(self.epd, bytes[offset..].ptr, bytes.len - offset, scif.send_block);
if (written <= 0) {
return VkError.InitializationFailed;
return VkError.DeviceLost;
}
offset += @intCast(written);
}
@@ -123,7 +152,7 @@ fn readAll(self: *Self, bytes: []u8) VkError!void {
while (offset < bytes.len) {
const read = scif.recv(self.epd, bytes[offset..].ptr, bytes.len - offset, scif.recv_block);
if (read <= 0) {
return VkError.InitializationFailed;
return VkError.DeviceLost;
}
offset += @intCast(read);
}
+58 -15
View File
@@ -4,6 +4,8 @@
#include <Buffer.h>
#include <Image.h>
#include <string.h>
static const char* CommandName[] = {
"CopyBuffer", "FillBuffer", "CopyBufferToImage", "CopyImageToBuffer", "CopyImage",
};
@@ -13,8 +15,15 @@ PhiStatus ReadCommandData(PhiCommandReader* reader, void* data, uint64_t size)
if(reader->remaining < size)
return PHI_STATUS_BAD_MESSAGE;
if(ReadAll(reader->endpoint, data, (size_t)size) < 0)
if(reader->memory != NULL)
{
memcpy(data, reader->memory, (size_t)size);
reader->memory += (size_t)size;
}
else if(ReadAll(reader->endpoint, data, (size_t)size) < 0)
{
return PHI_STATUS_BAD_MESSAGE;
}
reader->remaining -= size;
return PHI_STATUS_OK;
@@ -25,6 +34,13 @@ int DrainCommandReader(PhiCommandReader* reader)
if(reader->remaining == 0)
return 0;
if(reader->memory != NULL)
{
reader->memory += (size_t)reader->remaining;
reader->remaining = 0;
return 0;
}
int result = DrainPayload(reader->endpoint, reader->remaining);
reader->remaining = 0;
return result;
@@ -53,6 +69,45 @@ static PhiStatus ExecuteCommand(PhiCommandReader* reader, const PhiCmdHeader* co
return PHI_STATUS_BAD_MESSAGE;
}
static PhiStatus ExecuteCommands(PhiCommandReader* reader, uint64_t cmd_count)
{
PhiStatus status = PHI_STATUS_OK;
for(uint64_t cmd_index = 0; cmd_index < cmd_count; ++cmd_index)
{
PhiCmdHeader cmd_header;
status = ReadCommandHeader(reader, &cmd_header);
if(status != PHI_STATUS_OK)
break;
status = ExecuteCommand(reader, &cmd_header);
if(status != PHI_STATUS_OK)
{
const size_t command_name_count = sizeof(CommandName) / sizeof(CommandName[0]);
const char* command_name = cmd_header.type < command_name_count ? CommandName[cmd_header.type] : "Unknown";
LogErrorFmt("Command %s execution failed: %s", command_name, StatusName[status]);
break;
}
}
return status;
}
PhiStatus ExecuteCommandBuffer(const void* data, uint64_t size, uint64_t cmd_count)
{
PhiCommandReader reader = {
.endpoint = PHI_ENDPOINT_INVALID,
.memory = data,
.remaining = size,
};
const PhiStatus status = ExecuteCommands(&reader, cmd_count);
if(status != PHI_STATUS_OK)
return status;
return reader.remaining == 0 ? PHI_STATUS_OK : PHI_STATUS_BAD_MESSAGE;
}
int HandleWorkExecution(PhiEndpoint endpoint, const PhiMessageHeader* header)
{
PhiWorkExecutionRequest request;
@@ -75,6 +130,7 @@ int HandleWorkExecution(PhiEndpoint endpoint, const PhiMessageHeader* header)
PhiCommandReader reader = {
.endpoint = endpoint,
.memory = NULL,
.remaining = header->payload_size - sizeof(request),
};
@@ -86,20 +142,7 @@ int HandleWorkExecution(PhiEndpoint endpoint, const PhiMessageHeader* header)
return SendReply(endpoint, header, &reply, sizeof(reply));
}
for(uint64_t cmd_index = 0; cmd_index < request.cmd_count; ++cmd_index)
{
PhiCmdHeader cmd_header;
reply.result.status = ReadCommandHeader(&reader, &cmd_header);
if(reply.result.status != PHI_STATUS_OK)
break;
reply.result.status = ExecuteCommand(&reader, &cmd_header);
if(reply.result.status != PHI_STATUS_OK)
{
LogErrorFmt("Command %s execution failed: %s", CommandName[cmd_header.type], StatusName[reply.result.status]);
break;
}
}
reply.result.status = ExecuteCommands(&reader, request.cmd_count);
if(reader.remaining > 0 && DrainCommandReader(&reader) < 0)
return -1;
+2
View File
@@ -6,11 +6,13 @@
typedef struct PhiCommandReader
{
PhiEndpoint endpoint;
const uint8_t* memory;
uint64_t remaining;
} PhiCommandReader;
int HandleWorkExecution(PhiEndpoint endpoint, const PhiMessageHeader* header);
int DrainCommandReader(PhiCommandReader* reader);
PhiStatus ExecuteCommandBuffer(const void* data, uint64_t size, uint64_t cmd_count);
PhiStatus ReadCommandData(PhiCommandReader* reader, void* data, uint64_t size);
#endif
+7
View File
@@ -3,6 +3,7 @@
#include <Daemon.h>
#include <Logger.h>
#include <Memory.h>
#include <Queue.h>
static int HandleHello(PhiEndpoint endpoint, const PhiMessageHeader* header)
{
@@ -39,7 +40,10 @@ PhiEndpoint StartDaemon(void)
PhiEndpoint endpoint = TransportListen(PHI_TRANSPORT_PORT);
if(endpoint == PHI_ENDPOINT_INVALID)
{
LogError("Could not listen on the Phi transport");
return 0;
}
LogInfo("Daemon started");
return endpoint;
@@ -92,6 +96,9 @@ int HandlePacket(PhiEndpoint endpoint)
return -1;
break;
case PHI_PACKET_QUEUE_SETUP:
return HandleQueueSetup(endpoint, &header);
case PHI_PACKET_SHUTDOWN:
if(DrainPayload(endpoint, header.payload_size) < 0)
return -1;
+8 -4
View File
@@ -32,13 +32,15 @@
#define UNDERLINE_OFF 24
#define INVERSE_OFF 27
inline static void SetConsoleColor(FILE* file, int code)
static inline void SetConsoleColor(FILE* file, int code)
{
fprintf(file, "\033[1;%dm", code);
}
void Log(LogLevel level, const char* fmt, const char* file, const char* function, int line, ...)
{
#ifndef NOLOGS
time_t now = time(0);
struct tm tstruct = *localtime(&now);
char buffer[128];
@@ -86,11 +88,13 @@ void Log(LogLevel level, const char* fmt, const char* file, const char* function
fputc('\n', out);
fflush(out);
#endif
if(level == PHI_LOG_LEVEL_FATAL)
{
SetConsoleColor(out, BG_RED);
fprintf(out, "Fatal Error: emergency exit\n");
SetConsoleColor(out, BG_DEF);
SetConsoleColor(stderr, BG_RED);
fprintf(stderr, "Fatal Error: emergency exit\n");
SetConsoleColor(stderr, BG_DEF);
abort();
}
}
+152
View File
@@ -0,0 +1,152 @@
#include <Queue.h>
#include <CommandBuffer.h>
#include <Daemon.h>
#include <Logger.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
static PhiStatus ExecuteQueueSubmission(PhiEndpoint endpoint, const PhiQueueSubmission* submission)
{
if(submission->command_size == 0)
return submission->command_count == 0 ? PHI_STATUS_OK : PHI_STATUS_BAD_MESSAGE;
if(submission->command_count == 0)
return PHI_STATUS_BAD_MESSAGE;
void* commands = malloc((size_t)submission->command_size);
if(commands == NULL)
return PHI_STATUS_OUT_OF_MEMORY;
PhiStatus status = PHI_STATUS_OK;
if(TransportReadRemote(endpoint, commands, (size_t)submission->command_size, submission->command_scif_offset) < 0)
{
LogErrorFmt("Failed to read from host: %s", strerror(errno));
status = PHI_STATUS_INVALID_HANDLE;
}
else
status = ExecuteCommandBuffer(commands, submission->command_size, submission->command_count);
free(commands);
return status;
}
static int SendQueueCompletion(PhiEndpoint endpoint, uint64_t sequence, PhiStatus status)
{
const PhiQueueCompletion completion = {
.sequence = sequence,
.status = status,
.reserved = 0,
};
return WriteAll(endpoint, &completion, sizeof(completion));
}
// Returns 1 for a graceful queue shutdown, 0 when the peer disconnects, and -1 for a transport failure
static int RunQueue(PhiEndpoint endpoint, volatile PhiQueueShared* shared)
{
uint64_t next_sequence = 1;
PhiStatus fatal_status = PHI_STATUS_OK;
for(;;)
{
PhiQueueDoorbell doorbell;
if(ReadAll(endpoint, &doorbell, sizeof(doorbell)) < 0)
return 0;
if(doorbell.sequence == PHI_QUEUE_SHUTDOWN_SEQUENCE)
return 1;
if(doorbell.sequence < next_sequence)
continue;
while(next_sequence <= doorbell.sequence)
{
const size_t slot = (size_t)((next_sequence - 1u) % PHI_QUEUE_RING_CAPACITY);
__atomic_thread_fence(__ATOMIC_ACQUIRE);
const volatile PhiQueueSubmission* remote_submission = &shared->submissions[slot];
const PhiQueueSubmission submission = {
.sequence = remote_submission->sequence,
.command_scif_offset = remote_submission->command_scif_offset,
.command_size = remote_submission->command_size,
.command_count = remote_submission->command_count,
};
PhiStatus status = fatal_status;
if(status == PHI_STATUS_OK)
{
if(submission.sequence != next_sequence)
status = PHI_STATUS_BAD_MESSAGE;
else
status = ExecuteQueueSubmission(endpoint, &submission);
}
if(status != PHI_STATUS_OK && fatal_status == PHI_STATUS_OK)
{
LogErrorFmt("Queue submission %llu failed: %s", (unsigned long long)next_sequence, StatusName[status]);
fatal_status = status;
}
__atomic_store_n(&shared->completed_sequence, next_sequence, __ATOMIC_RELEASE);
if(SendQueueCompletion(endpoint, next_sequence, status) < 0)
return -1;
++next_sequence;
}
}
}
int HandleQueueSetup(PhiEndpoint endpoint, const PhiMessageHeader* header)
{
PhiQueueSetupRequest request;
PhiResultReply reply = {
.result = {
.status = PHI_STATUS_OK,
.reserved = 0,
},
};
if(header->payload_size != sizeof(request))
{
if(DrainPayload(endpoint, header->payload_size) < 0)
return -1;
reply.result.status = PHI_STATUS_BAD_MESSAGE;
return SendReply(endpoint, header, &reply, sizeof(reply));
}
if(ReadAll(endpoint, &request, sizeof(request)) < 0)
return -1;
if(request.ring_capacity != PHI_QUEUE_RING_CAPACITY || request.scif_size < sizeof(PhiQueueShared))
{
reply.result.status = PHI_STATUS_INVALID_ARGUMENT;
return SendReply(endpoint, header, &reply, sizeof(reply));
}
volatile PhiQueueShared* shared =
scif_mmap(NULL, (size_t)request.scif_size, PROT_READ | PROT_WRITE, 0, endpoint, request.scif_offset);
if(shared == MAP_FAILED)
{
reply.result.status = PHI_STATUS_MAP_HOST_MEMORY_FAILED;
return SendReply(endpoint, header, &reply, sizeof(reply));
}
if(SendReply(endpoint, header, &reply, sizeof(reply)) < 0)
{
scif_munmap((void*)shared, (size_t)request.scif_size);
return -1;
}
const int run_result = RunQueue(endpoint, shared);
if(scif_munmap((void*)shared, (size_t)request.scif_size) != 0)
return -1;
if(run_result == 1)
return SendQueueCompletion(endpoint, PHI_QUEUE_SHUTDOWN_SEQUENCE, PHI_STATUS_OK);
return run_result;
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef APE_PHI_QUEUE_H
#define APE_PHI_QUEUE_H
#include <Protocol.h>
#include <Transport.h>
int HandleQueueSetup(PhiEndpoint endpoint, const PhiMessageHeader* header);
#endif
+7
View File
@@ -28,6 +28,13 @@ PhiEndpoint TransportListen(uint16_t port)
return endpoint;
}
int TransportReadRemote(PhiEndpoint endpoint, void* data, size_t size, uint64_t remote_offset)
{
if(size == 0)
return 0;
return scif_vreadfrom(endpoint, data, size, (off_t)remote_offset, SCIF_RMA_SYNC);
}
ssize_t TransportReceive(PhiEndpoint endpoint, void* data, size_t size)
{
return scif_recv(endpoint, data, size, SCIF_RECV_BLOCK);
+1
View File
@@ -14,6 +14,7 @@ PhiEndpoint TransportAccept(PhiEndpoint endpoint);
int TransportClose(PhiEndpoint endpoint);
PhiEndpoint TransportListen(uint16_t port);
int TransportReadRemote(PhiEndpoint endpoint, void* data, size_t size, uint64_t remote_offset);
ssize_t TransportReceive(PhiEndpoint endpoint, void* data, size_t size);
ssize_t TransportSend(PhiEndpoint endpoint, const void* data, size_t size);
+46 -1
View File
@@ -7,9 +7,12 @@
#define PHI_MEMORY_ALIGNMENT 64
#define PHI_PROTOCOL_MAGIC 0x50484941u
#define PHI_PROTOCOL_VERSION 1u
#define PHI_PROTOCOL_VERSION 2u
#define PHI_SCIF_PORT 43616u
#define PHI_QUEUE_RING_CAPACITY 64u
#define PHI_QUEUE_SHUTDOWN_SEQUENCE UINT64_MAX
#ifndef PHI_TRANSPORT_PORT
#define PHI_TRANSPORT_PORT PHI_SCIF_PORT
#endif
@@ -24,6 +27,7 @@ typedef enum PhiPacketType
PHI_PACKET_WORK_EXECUTION = 6,
PHI_PACKET_SHUTDOWN = 7,
PHI_PACKET_MAP_HOST_MEMORY = 8,
PHI_PACKET_QUEUE_SETUP = 9,
} PhiPacketType;
// When adding status, update StatusName in Logger.h
@@ -104,4 +108,45 @@ typedef struct PhiWorkExecutionRequest
uint64_t command_buffer_size;
} PhiWorkExecutionRequest;
typedef struct PhiQueueSubmission
{
uint64_t sequence;
uint64_t command_scif_offset;
uint64_t command_size;
uint64_t command_count;
} PhiQueueSubmission;
typedef struct PhiQueueShared
{
// Host-written producer timeline. Keep it on its own cache line
uint64_t producer_sequence;
uint8_t producer_padding[56];
// MIC-written completion timeline. Keep it on its own cache line
uint64_t completed_sequence;
uint8_t completion_padding[56];
PhiQueueSubmission submissions[PHI_QUEUE_RING_CAPACITY];
} PhiQueueShared;
typedef struct PhiQueueSetupRequest
{
uint64_t scif_offset;
uint64_t scif_size;
uint32_t ring_capacity;
uint32_t reserved;
} PhiQueueSetupRequest;
typedef struct PhiQueueDoorbell
{
uint64_t sequence;
} PhiQueueDoorbell;
typedef struct PhiQueueCompletion
{
uint64_t sequence;
int32_t status;
uint32_t reserved;
} PhiQueueCompletion;
#endif
-14
View File
@@ -106,25 +106,11 @@ pub inline fn bindSparse(self: *Self, info: []const vk.BindSparseInfo, fence: ?*
}
pub fn submit(self: *Self, infos: []const vk.SubmitInfo, p_fence: ?*Fence) VkError!void {
if (infos.len == 0) {
if (p_fence) |fence| {
try fence.signal();
}
return;
}
const allocator = self.host_allocator.cloneWithScope(.command).allocator();
var submit_infos = try SubmitInfo.initBlob(allocator, infos);
defer SubmitInfo.deinitBlob(allocator, &submit_infos);
if (submit_infos.items.len == 0) {
if (p_fence) |fence| {
try fence.signal();
}
return;
}
try self.dispatch_table.submit(self, submit_infos.items, p_fence);
}