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

Yielding Behaviours #27

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
10 changes: 9 additions & 1 deletion src/rt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ endif()

target_compile_definitions(verona_rt INTERFACE -DSNMALLOC_CHEAP_CHECKS)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)

if(COROUTINES)
if(MSVC)
target_compile_options(verona_rt INTERFACE /await)
else()
target_compile_options(verona_rt INTERFACE -fcoroutines)
endif()
endif()

warnings_high()
75 changes: 75 additions & 0 deletions src/rt/cpp/coro.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright Microsoft and Project Verona Contributors.
// SPDX-License-Identifier: MIT
#pragma once

#include <coroutine>
#include <iostream>

namespace verona::cpp
{
struct promise;

struct coroutine
{
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;

struct promise_type
{
coroutine get_return_object()
{
return {handle_type::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept
{
return {};
}
std::suspend_always final_suspend() noexcept
{
return {};
}
void unhandled_exception() {}
void return_void() {}
};

handle_type h_;
bool initialized = false;

coroutine(handle_type h) : h_(h), initialized(true) {}
coroutine() : h_(nullptr), initialized(false) {}

void resume() const
{
h_.resume();
}

bool done() const
{
return h_.done();
}
};

template<typename F>
auto prepare_coro_lambda(F&& f)
{
coroutine coro_state;

auto coro_f = [f = std::move(f),
coro_state = std::move(coro_state)](auto... args) mutable {
if (coro_state.initialized == false)
{
coro_state = std::move(f(args...));
coro_state.resume();
}
else
{
if (!(coro_state.done()))
{
coro_state.resume();
}
}
};

return coro_f;
}
} // namespace verona::cpp
27 changes: 27 additions & 0 deletions src/rt/cpp/when.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#pragma once

#include "../sched/behaviour.h"
#include "coro.h"
#include "cown.h"

#include <functional>
Expand Down Expand Up @@ -263,6 +264,32 @@ namespace verona::cpp
std::make_tuple(When(std::forward<F>(f), std::move(cown_tuple))));
}
}

/**
* This operator is used to schedule coroutines instead of lambdas that
* run to completion. Unlike other lambdas, these coroutines should return
* a coroutine and their arguments should be references to acquired_cown
* instead of acquired_cown objects.
*/
template<typename F>
auto operator<<=(F&& f)
{
Scheduler::stats().behaviour(sizeof...(Args));

auto coro_f = prepare_coro_lambda(f);

if constexpr (sizeof...(Args) == 0)
{
// Execute now atomic batch makes no sense.
verona::rt::schedule_lambda(std::forward<decltype(coro_f)>(coro_f));
return Batch(std::make_tuple());
}
else
{
return Batch(std::make_tuple(
When(std::forward<decltype(coro_f)>(coro_f), std::move(cown_tuple))));
}
}
};

/**
Expand Down
9 changes: 9 additions & 0 deletions src/rt/sched/behaviour.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

namespace verona::rt
{
static thread_local bool behaviour_yielded;

/**
* This class provides the full `when` functionality. It
* provides the closure and lifetime management for the class.
Expand All @@ -20,6 +22,13 @@ namespace verona::rt
Be* body = behaviour->get_body<Be>();
(*body)();

if (behaviour_yielded)
{
behaviour_yielded = false;
work->yielded = true;
return;
}

behaviour->release_all();

// Dealloc behaviour
Expand Down
8 changes: 7 additions & 1 deletion src/rt/sched/schedulerthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,15 @@ namespace verona::rt
while ((work = get_work(batch)))
{
Logging::cout() << "Schedule work " << work << Logging::endl;

work->run();

if (work->yielded)
{
work->yielded = false;
core->q.enqueue(work);
}

yield();
}

Expand Down
2 changes: 2 additions & 0 deletions src/rt/sched/work.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ namespace verona::rt
{
static constexpr auto NO_EPOCH_SET = (std::numeric_limits<uint64_t>::max)();

bool yielded = false;

// Entry in the MPMC Queue of work items per scheduler.
union
{
Expand Down
2 changes: 1 addition & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ foreach(TEST_MODE "sys" "con")
add_dependencies(rt_tests ${TESTNAME})
target_link_libraries(${TESTNAME} verona_rt)
if (${TEST_MODE} STREQUAL "sys")
target_compile_definitions(${TESTNAME} PRIVATE USE_SYSTEMATIC_TESTING)
target_compile_definitions(${TESTNAME} PRIVATE USE_SYSTEMATIC_TESTING COROUTINES)
else()
add_test("runtime/${TESTNAME}" ${TESTRUNNER} ${TESTNAME})
if (VERONA_CI_BUILD)
Expand Down
48 changes: 48 additions & 0 deletions test/func/coro/coro.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright Microsoft and Project Verona Contributors.
// SPDX-License-Identifier: MIT

#include <cpp/coro.h>
#include <cpp/when.h>
#include <debug/harness.h>

using namespace verona::cpp;

class Body
{
public:
int counter;

~Body()
{
Logging::cout() << "Body destroyed" << Logging::endl;
}
};

void test_body()
{
Logging::cout() << "test_body()" << Logging::endl;

auto log1 = make_cown<Body>();

when(log1) <<= [=](acquired_cown<Body>& acq) -> coroutine {
std::cout << "counter = " << acq->counter << std::endl;

acq->counter++;
verona::rt::behaviour_yielded = true;
co_await std::suspend_always{};

std::cout << "counter = " << acq->counter << std::endl;
std::cout << "end" << std::endl;
};
}

int main(int argc, char** argv)
{
SystematicTestHarness harness(argc, argv);

Logging::cout() << "Yield test" << Logging::endl;

harness.run(test_body);

return 0;
}
56 changes: 56 additions & 0 deletions test/func/yield/yield.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright Microsoft and Project Verona Contributors.
// SPDX-License-Identifier: MIT

#include <cpp/when.h>
#include <debug/harness.h>

#define yield() \
verona::rt::behaviour_yielded = true; \
return

class Body
{
public:
int counter;
};

using namespace verona::cpp;

void test_body()
{
auto log1 = make_cown<Body>();
auto log2 = make_cown<Body>();

when(log2) << [](auto l) {
Logging::cout() << "Short running task starting ..... " << Logging::endl;
Logging::cout() << "Short running task finished counter = "
<< Logging::endl;
};

when(log1) << [](auto l) {
Logging::cout() << "Long running task starting counter value = "
<< l->counter << Logging::endl;

while (l->counter < 100 && !verona::rt::behaviour_yielded)
{
l->counter++;
if (l->counter % 10 == 0)
{
Logging::cout() << "Yielding at counter = " << l->counter
<< Logging::endl;
yield();
}
}
};
}

int main(int argc, char** argv)
{
SystematicTestHarness harness(argc, argv);

Logging::cout() << "Yield test starting" << Logging::endl;

harness.run(test_body);

return 0;
}
Loading