Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple Lock (Mutex) implementation and test #11

Merged
merged 3 commits into from
Apr 4, 2024
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
119 changes: 119 additions & 0 deletions include/pando-lib-galois/sync/simple_lock.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2023. University of Texas at Austin. All rights reserved.

#ifndef PANDO_LIB_GALOIS_SYNC_SIMPLE_LOCK_HPP_
#define PANDO_LIB_GALOIS_SYNC_SIMPLE_LOCK_HPP_

#include <pando-rt/export.h>

#include <pando-rt/memory/allocate_memory.hpp>
#include <pando-rt/memory/global_ptr.hpp>
#include <pando-rt/pando-rt.hpp>
#include <pando-rt/sync/atomic.hpp>

namespace galois {
/**
* @brief This is a termination detection mechanism that is used for detecting nested parallelism
*/
class SimpleLock {
using LockState = std::uint64_t;
enum class State : LockState { IsUnlocked = 0, IsLocked = 1 };

/**
* @brief This is a pointer to the state used by everyone
*/
pando::GlobalPtr<LockState> m_state = nullptr;

public:
// construct
constexpr SimpleLock() noexcept = default;
// movable
constexpr SimpleLock(SimpleLock&&) noexcept = default;
// copyable
constexpr SimpleLock(const SimpleLock&) noexcept = default;
// destruct
~SimpleLock() = default;

// copy-assignable
constexpr SimpleLock& operator=(const SimpleLock&) noexcept = default;
// move-assignable
constexpr SimpleLock& operator=(SimpleLock&&) noexcept = default;

/**
* @brief initializes the SimpleLock
*
* @param[in] place the location the counter should be allocated at
* @param[in] memoryType the type of memory the waitgroup should be allocated in
*
* @warning one of the initialize methods must be called before use
*/
[[nodiscard]] pando::Status initialize(pando::Place place, pando::MemoryType memoryType) {
// auto desiredValue = static_cast<LockState>(State::IsUnlocked);
// pando::atomicStore(pando::GlobalPtr<LockState>(&m_state), pando::GlobalPtr<const
// LockState>(&desiredValue),
// std::memory_order_release);

const auto desiredValue = pando::allocateMemory<LockState>(1, place, memoryType);
if (!desiredValue.hasValue()) {
return desiredValue.error();
}
m_state = desiredValue.value();
*m_state = static_cast<LockState>(State::IsUnlocked);
pando::atomicThreadFence(std::memory_order_release);
return pando::Status::Success;
}

/**
* @brief initializes the SimpleLock
*
* @warning one of the initialize methods must be called before use
*/
[[nodiscard]] pando::Status initialize() {
return initialize(pando::getCurrentPlace(), pando::MemoryType::Main);
}

/**
* @brief deinitializes the SimpleLock and frees associated memory
*
* @warning not threadsafe but designed to be idempotent.
*/
void deinitialize() {
if (m_state != nullptr) {
pando::deallocateMemory(m_state, 1);
m_state = nullptr;
}
}

/**
* @brief Acquire the lock or block until the lock is acquired.
*/
void lock() {
while (!try_lock()) {}
}

/**
* @brief Attempt to acquire the lock and returns immediately on success or failure.
*
* @return true If the lock was successfully acquired.
* @return false If the lock acquire attempt failed.
*/
bool try_lock() {
constexpr auto success = std::memory_order_acquire;
constexpr auto failure = std::memory_order_relaxed;
auto expected = static_cast<LockState>(State::IsUnlocked);
auto desired = static_cast<LockState>(State::IsLocked);
return pando::atomicCompareExchange(m_state, pando::GlobalPtr<LockState>(&expected),
pando::GlobalPtr<const LockState>(&desired), success,
failure);
}
/**
* @brief Release the lock.
*/
void unlock() {
auto desiredValue = static_cast<LockState>(State::IsUnlocked);
pando::atomicStore(m_state, pando::GlobalPtr<const LockState>(&desiredValue),
std::memory_order_release);
}
};
} // namespace galois
#endif // PANDO_LIB_GALOIS_SYNC_SIMPLE_LOCK_HPP_
1 change: 1 addition & 0 deletions test/sync/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2023. University of Texas at Austin. All rights reserved.

pando_add_driver_test(test_simple_lock test_simple_lock.cpp)
pando_add_driver_test(test_global_barrier test_global_barrier.cpp)
pando_add_driver_test(test_wait_group test_wait_group.cpp)
pando_add_driver_test(test_atomic test_atomic.cpp)
96 changes: 96 additions & 0 deletions test/sync/test_simple_lock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2023. University of Texas at Austin. All rights reserved.

#include <gtest/gtest.h>
#include <pando-rt/export.h>

#include <cstdint>

#include <pando-lib-galois/loops/do_all.hpp>
#include <pando-lib-galois/sync/global_barrier.hpp>
#include <pando-lib-galois/sync/simple_lock.hpp>
#include <pando-rt/containers/vector.hpp>
#include <pando-rt/memory/global_ptr.hpp>
#include <pando-rt/pando-rt.hpp>

TEST(SimpleLock, TryLock) {
auto test = [] {
galois::SimpleLock mutex;
EXPECT_EQ(mutex.initialize(), pando::Status::Success);
const bool wasUnlocked = mutex.try_lock();
EXPECT_TRUE(wasUnlocked);

const bool isUnlocked = mutex.try_lock();
EXPECT_FALSE(isUnlocked);

mutex.unlock();
mutex.deinitialize();

return true;
};

const auto thisPlace = pando::getCurrentPlace();
const auto place = pando::Place{thisPlace.node, pando::anyPod, pando::anyCore};
auto result = pando::executeOnWait(place, +test);
EXPECT_TRUE(result.hasValue());
}

TEST(SimpleLock, SimpleLockUnlock) {
auto test = [] {
galois::SimpleLock mutex;
EXPECT_EQ(mutex.initialize(), pando::Status::Success);
mutex.lock();
mutex.unlock();
mutex.deinitialize();
return true;
};

const auto thisPlace = pando::getCurrentPlace();
const auto place = pando::Place{thisPlace.node, pando::anyPod, pando::anyCore};
auto result = pando::executeOnWait(place, +test);
EXPECT_TRUE(result.hasValue());
}

TEST(SimpleLock, ActualLockUnlock) {
auto dims = pando::getPlaceDims();
galois::GlobalBarrier gb;
EXPECT_EQ(gb.initialize(dims.node.id), pando::Status::Success);
galois::SimpleLock mutex;
EXPECT_EQ(mutex.initialize(), pando::Status::Success);
pando::Array<int> array;
EXPECT_EQ(array.initialize(10), pando::Status::Success);
array.fill(0);

auto func = +[](galois::GlobalBarrier gb, galois::SimpleLock mutex, pando::Array<int> array) {
mutex.lock();
for (int i = 0; i < 10; i++) {
if ((i + 1 + pando::getCurrentPlace().node.id) <= 10) {
array[i] = i + 1 + pando::getCurrentPlace().node.id;
} else {
array[i] = i - 9 + pando::getCurrentPlace().node.id;
}
}
mutex.unlock();
gb.done();
};
for (std::int16_t nodeId = 0; nodeId < dims.node.id; nodeId++) {
EXPECT_EQ(
pando::executeOn(pando::Place{pando::NodeIndex{nodeId}, pando::anyPod, pando::anyCore},
func, gb, mutex, array),
pando::Status::Success);
}

EXPECT_EQ(gb.wait(), pando::Status::Success);
for (int i = 0; i < 10; i++) {
std::cout << array[i] << " ";
}
std::cout << std::endl;
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += array[i];
}
EXPECT_EQ(sum, 55);

gb.deinitialize();
array.deinitialize();
}
Loading