Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Code/max/Hardware/CPU/Task.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2025, The max Contributors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <max/Hardware/CPU/Task.hpp>

namespace max {
namespace Hardware {
namespace CPU {

Task::~Task() noexcept = default;

} // namespace CPU
} // namespace Hardware
} // namespace max
25 changes: 25 additions & 0 deletions Code/max/Hardware/CPU/Task.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2025, The max Contributors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MAX_HARDWARE_CPU_TASK_HPP
#define MAX_HARDWARE_CPU_TASK_HPP

namespace max {
namespace Hardware {
namespace CPU {

class Task {
public:

virtual ~Task() noexcept;

virtual void Run() noexcept = 0;

};

} // namespace CPU
} // namespace Hardware
} // namespace max

#endif // #ifndef MAX_HARDWARE_CPU_TASK_HPP
124 changes: 124 additions & 0 deletions Code/max/Hardware/CPU/TaskQueue.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright 2025, The max Contributors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <max/Hardware/CPU/TaskQueue.hpp>

#include <type_traits>

#include <utility>

namespace max {
namespace Hardware {
namespace CPU {

TaskQueue::TaskQueue(HANDLE wake_event) noexcept
: task_queue_mutex_()
, task_queue_()
, wake_event_(std::move(wake_event))
, shutting_down_(false)
{
}

TaskQueue::~TaskQueue() noexcept {
BOOL result = CloseHandle(wake_event_);
if (result == 0) {
// GetLastError()
}
}

TaskQueue::AddTaskError TaskQueue::AddTask(std::unique_ptr<Task> task) noexcept {
if (shutting_down_) {
return AddTaskError::ShuttingDown;
}

{
std::lock_guard<std::mutex> task_queue_guard(task_queue_mutex_);
task_queue_.push(std::move(task));
BOOL result = SetEvent(wake_event_);
if (result == 0) {
// GetLastError()
return AddTaskError::CouldNotSetEvent;
}
}

return AddTaskError::Okay;
}

std::unique_ptr<Task> TaskQueue::TryPopTask() noexcept {
std::lock_guard<std::mutex> task_queue_guard(task_queue_mutex_);

BOOL result = ResetEvent(wake_event_);
if (result == 0) {
// GetLastError
return nullptr;
}

if (task_queue_.empty()) {
return nullptr;
}

std::unique_ptr<Task> task = std::move(task_queue_.front());
task_queue_.pop();
return task;
}

TaskQueue::WaitForEventError TaskQueue::WaitForEvent() noexcept {
auto result = WaitForMultipleObjects(1, &wake_event_, TRUE, INFINITE);
if (result != WAIT_OBJECT_0) {
return WaitForEventError::CouldNotWait;
} else {
result = ResetEvent(wake_event_);
if (result == 0) {
// GetLastError()
return WaitForEventError::CouldNotResetEvent;
}
}

return WaitForEventError::Okay;
}

TaskQueue::ShutdownError TaskQueue::Shutdown() noexcept {
std::lock_guard<std::mutex> task_queue_guard(task_queue_mutex_);

shutting_down_ = true;
BOOL result = SetEvent(wake_event_);
if (result == 0) {
// GetLastError()
return ShutdownError::CouldNotSetEvent;
}

return ShutdownError::Okay;
}

std::expected<std::unique_ptr<TaskQueue>, CreateTaskQueueError> CreateTaskQueue() noexcept {
HANDLE wake_event = CreateEvent(NULL, TRUE, FALSE, TEXT("wake_event_"));
if (wake_event == NULL) {
// GetLastError()
return std::unexpected(CreateTaskQueueError::CouldNotCreate);
}

return std::make_unique<TaskQueue>(std::move(wake_event));
}

void TaskRunnerLoop(TaskQueue* task_queue) noexcept {
bool continue_looping = true;
do {
auto task = task_queue->TryPopTask();
if (task) {
task->Run();
}
else {
if (task_queue->shutting_down_) {
continue_looping = false;
}
else {
task_queue->WaitForEvent();
}
}
} while (continue_looping);
}

} // namespace CPU
} // namespace Hardware
} // namespace max
89 changes: 89 additions & 0 deletions Code/max/Hardware/CPU/TaskQueue.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2025, The max Contributors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MAX_HARDWARE_CPU_TASKQUEUE_HPP
#define MAX_HARDWARE_CPU_TASKQUEUE_HPP

#include <expected>
#include <memory>
#include <mutex>
#include <queue>


// TODO: Handle other platforms
// TODO: This is only needed for the definition of HANDLE. Can we remove this somehow?
#ifndef WIN32_LEAN_AND_MEAN
#define WIn32_LEAN_AND_MEAN
#endif
#include <Windows.h>

#include <max/Hardware/CPU/Task.hpp>

namespace max {
namespace Hardware {
namespace CPU {

// TaskQueue uses std::mutex, which cannot be copied nor moved.
// This restriction has a major impact on the design of the class.
// Instead of the usual std::expected<T, ErrorCode> CreateT() function,
// we use std::expected<std::unique_ptr<T, ErrorCode> CreateT().
//
// The additional unique_ptr means the actual object doesn't have to move.

// Assume a 64-byte cache line size, and align to that.
// This is so a vector of TaskQueues does not cause false sharing between threads.
// TODO: VC warns that alignas(64) did indeed pad. And we treat warnings as errors.
class /*alignas(64)*/ TaskQueue {
public:

// TODO: This is only public so std::make_unique can call it
TaskQueue(HANDLE wake_event) noexcept;
~TaskQueue() noexcept;

enum class AddTaskError {
Okay,
ShuttingDown,
CouldNotSetEvent,
};
AddTaskError AddTask(std::unique_ptr<Task> task) noexcept;

// TODO: Should this return a std::expected so it can provide error values?
std::unique_ptr<Task> TryPopTask() noexcept;

enum class WaitForEventError {
Okay,
CouldNotWait,
CouldNotResetEvent,
};
WaitForEventError WaitForEvent() noexcept;

enum class ShutdownError {
Okay,
CouldNotSetEvent,
};
ShutdownError Shutdown() noexcept;

volatile bool shutting_down_;

private:

std::mutex task_queue_mutex_;
std::queue<std::unique_ptr<Task>> task_queue_;
HANDLE wake_event_;

};

enum class CreateTaskQueueError {
CouldNotCreate,
};
// TODO: Possibly provide an allocator, to allocate the TaskQueue at the right location
std::expected<std::unique_ptr<TaskQueue>, CreateTaskQueueError> CreateTaskQueue() noexcept;

void TaskRunnerLoop(TaskQueue* task_queue) noexcept;

} // namespace CPU
} // namespace Hardware
} // namespace max

#endif // #ifndef MAX_HARDWARE_CPU_TASKQUEUE_HPP
33 changes: 33 additions & 0 deletions Code/max/Hardware/CPU/TaskThread.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2025, The max Contributors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <max/Hardware/CPU/TaskThread.hpp>

#include <utility>

namespace max {
namespace Hardware {
namespace CPU {

TaskThread::TaskThread(std::unique_ptr<max::Hardware::CPU::TaskQueue> task_queue, std::thread thread) noexcept
: task_queue_(std::move(task_queue))
, thread_(std::move(thread))
{}

std::optional<TaskThread> CreateTaskThread() noexcept {
auto create_task_queue_result = max::Hardware::CPU::CreateTaskQueue();
if (!create_task_queue_result) {
return std::nullopt;
}
std::unique_ptr<max::Hardware::CPU::TaskQueue> task_queue = std::move(*create_task_queue_result);

auto thread = std::thread(max::Hardware::CPU::TaskRunnerLoop, task_queue.get());
// TODO: Attempt to lock that thread to the preferred type of core.

return TaskThread(std::move(task_queue), std::move(thread));
}

} // namespace CPU
} // namespace Hardware
} // namespace max
34 changes: 34 additions & 0 deletions Code/max/Hardware/CPU/TaskThread.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2025, The max Contributors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MAX_HARDWARE_CPU_TASKTHREAD_HPP
#define MAX_HARDWARE_CPU_TASKTHREAD_HPP

#include <memory>
#include <optional>
#include <thread>

#include <max/Hardware/CPU/TaskQueue.hpp>

namespace max {
namespace Hardware {
namespace CPU {

class TaskThread {
public:

explicit TaskThread(std::unique_ptr<max::Hardware::CPU::TaskQueue> task_queue, std::thread thread) noexcept;

std::unique_ptr<max::Hardware::CPU::TaskQueue> task_queue_;
std::thread thread_;

};

std::optional<TaskThread> CreateTaskThread() noexcept;

} // namespace CPU
} // namespace Hardware
} // namespace max

#endif // #ifndef MAX_HARDWARE_CPU_TASKTHREAD_HPP
6 changes: 6 additions & 0 deletions Projects/VisualStudio/max/max.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\Prefetch.hpp" />
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\Task.hpp" />
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TaskQueue.hpp" />
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TaskThread.hpp" />
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TLB.hpp" />
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TraceCache.hpp" />
<ClInclude Include="..\..\..\Code\max\Logging\DoNothingLogger.hpp" />
Expand Down Expand Up @@ -305,6 +308,9 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\Prefetch.cpp" />
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\Task.cpp" />
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TaskQueue.cpp" />
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TaskThread.cpp" />
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TLB.cpp" />
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TraceCache.cpp" />
<ClCompile Include="..\..\..\Code\max\Logging\DoNothingLogger.cpp" />
Expand Down
18 changes: 18 additions & 0 deletions Projects/VisualStudio/max/max.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,15 @@
<ClInclude Include="..\..\..\Code\max\Compiling\Bitmask.hpp">
<Filter>Code\max\Compiling</Filter>
</ClInclude>
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TaskThread.hpp">
<Filter>Code\max\Hardware\CPU</Filter>
</ClInclude>
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\Task.hpp">
<Filter>Code\max\Hardware\CPU</Filter>
</ClInclude>
<ClInclude Include="..\..\..\Code\max\Hardware\CPU\TaskQueue.hpp">
<Filter>Code\max\Hardware\CPU</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\Code\max\Algorithms\IsBetweenTest.cpp">
Expand Down Expand Up @@ -479,6 +488,15 @@
<ClCompile Include="..\..\..\Code\max\Containers\RectangleTest.cpp">
<Filter>Code\max\Containers</Filter>
</ClCompile>
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TaskThread.cpp">
<Filter>Code\max\Hardware\CPU</Filter>
</ClCompile>
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\Task.cpp">
<Filter>Code\max\Hardware\CPU</Filter>
</ClCompile>
<ClCompile Include="..\..\..\Code\max\Hardware\CPU\TaskQueue.cpp">
<Filter>Code\max\Hardware\CPU</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<MASM Include="..\..\..\Code\max\Hardware\CPU\IsCPUIDAvailablePolicies\max_Hardware_CPU_X64AssemblyIsCPUIDAvailablePolicies_IsCPUIDAvailable.asm">
Expand Down
Loading