Skip to content
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
187 changes: 114 additions & 73 deletions src/rt/cpp/when.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "cown.h"
#include "cown_array.h"

#include <cstring>
#include <functional>
#include <tuple>
#include <utility>
Expand Down Expand Up @@ -140,7 +141,7 @@ namespace verona::cpp
template<typename T>
auto convert_access(cown_ptr<T>&& c)
{
return Access<T>(c);
return Access<T>(std::move(c));
}

template<typename T>
Expand All @@ -149,74 +150,117 @@ namespace verona::cpp
return AccessBatch<T>(c);
}

template<typename... Args>
class DynamicAtomicBatch;

template<int bs>
class Batch
{
/// This is a tuple of
/// (exists Ts. When<Ts>)
/// As existential types are not supported this is using inferred template
/// parameters.
std::tuple<Args...> when_batch;

/// This is used to prevent the destructor from scheduling the behaviour
/// more than once.
/// If this batch is combined with another batch, then the destructor of
/// the uncombined batches should not run.
bool part_of_larger_batch = false;

template<typename... Args2>
friend class Batch;
BehaviourCore* barray[bs];

template<size_t index = 0>
void create_behaviour(BehaviourCore** barray)
friend class DynamicAtomicBatch;

public:
Batch()
{
if constexpr (index >= sizeof...(Args))
{
return;
}
else
{
auto&& w = std::get<index>(when_batch);
// Add the behaviour here
auto t = w.to_tuple();
barray[index] = Behaviour::prepare_to_schedule<
typename std::remove_reference<decltype(std::get<2>(t))>::type>(
std::move(std::get<0>(t)),
std::move(std::get<1>(t)),
std::move(std::get<2>(t)));
create_behaviour<index + 1>(barray);
}
assert(0);
}

public:
Batch(std::tuple<Args...> args) : when_batch(std::move(args)) {}
Batch(BehaviourCore* b)
{
assert(0);
}

Batch(const Batch&) = delete;

Batch(BehaviourCore** b1, BehaviourCore** b2, size_t s1, size_t s2)
{
std::memcpy(barray, b1, s1 * sizeof(BehaviourCore*));
std::memcpy(&barray[s1], b2, s2 * sizeof(BehaviourCore*));
}

~Batch()
{
if constexpr (sizeof...(Args) > 0)
if constexpr (bs > 0)
{
if (part_of_larger_batch)
return;

BehaviourCore* barray[sizeof...(Args)];
create_behaviour(barray);

BehaviourCore::schedule_many(barray, sizeof...(Args));
BehaviourCore::schedule_many(barray, bs);
}
}

template<typename... Args2>
auto operator+(Batch<Args2...>&& wb)
template<int bs2>
auto operator+(Batch<bs2>&& wb)
{
wb.part_of_larger_batch = true;
this->part_of_larger_batch = true;
return Batch<Args..., Args2...>(
std::tuple_cat(std::move(this->when_batch), std::move(wb.when_batch)));
return Batch<bs + bs2>(barray, wb.barray, bs, bs2);
}
};

/**
* This class does not handle reference counting for cowns.
* Make sure the cown_ptrs will be available till scheduling happens
*/
class DynamicAtomicBatch
{
BehaviourCore** barray;
size_t size;

public:
DynamicAtomicBatch() : barray(nullptr), size(0) {}
~DynamicAtomicBatch()
{
if (size == 0)
return;

BehaviourCore::schedule_many(barray, size);
delete barray;
}

template<int bs>
auto operator+(Batch<bs>&& b)
{
// Batch has a When, which holds a reference to a cown_ptr, need to keep
// this reference around!
b.part_of_larger_batch = true;

auto new_array = new BehaviourCore*[size + bs];

if (barray)
{
std::memcpy(new_array, barray, size * sizeof(BehaviourCore*));
delete barray;
}

barray = new_array;
std::memcpy(&barray[size], b.barray, bs * sizeof(BehaviourCore*));

size += bs;

return this;
}

DynamicAtomicBatch(const DynamicAtomicBatch&) = delete;
DynamicAtomicBatch(DynamicAtomicBatch&&) = delete;
};

template<>
Batch<0>::Batch()
{}

template<>
Batch<1>::Batch(BehaviourCore* b)
{
barray[0] = b;
}

/**
* Represents a single when statement.
*
Expand All @@ -242,7 +286,7 @@ namespace verona::cpp
struct is_batch<AccessBatch<T>> : std::true_type
{};

template<typename... Args2>
template<int bs>
friend class Batch;

/// Set of cowns used by this behaviour.
Expand All @@ -263,6 +307,8 @@ namespace verona::cpp
Request* req_extended;
bool is_req_extended;

BehaviourCore* behaviour_body;

/**
* This uses template programming to turn the std::tuple into a C style
* stack allocated array.
Expand Down Expand Up @@ -372,11 +418,12 @@ namespace verona::cpp
return acquired_cown<C>(*c.t);
}

auto to_tuple()
template<size_t index = 0>
void create_behaviour_new()
{
if constexpr (sizeof...(Args) == 0)
if constexpr (index >= sizeof...(Args))
{
return std::make_tuple(std::forward<F>(f));
return;
}
else
{
Expand All @@ -388,18 +435,19 @@ namespace verona::cpp

size_t count = array_assign(r);

return std::make_tuple(
count,
r,
[f = std::move(f), cown_tuple = std::move(cown_tuple)]() mutable {
/// Effectively converts ActualCown<T>... to
/// acquired_cown... .
auto lift_f = [f = std::move(f)](Args... args) mutable {
std::move(f)(access_to_acquired<typename Args::Type>(args)...);
};

std::apply(std::move(lift_f), std::move(cown_tuple));
});
auto closure = [f = std::move(f),
cown_tuple = std::move(cown_tuple)]() mutable {
/// Effectively converts ActualCown<T>... to
/// acquired_cown... .
auto lift_f = [f = std::move(f)](Args... args) mutable {
std::move(f)(access_to_acquired<typename Args::Type>(args)...);
};

std::apply(std::move(lift_f), std::move(cown_tuple));
};
behaviour_body = Behaviour::prepare_to_schedule<
typename std::remove_reference<decltype(closure)>::type>(
count, r, std::move(closure));
}
}

Expand All @@ -418,18 +466,12 @@ namespace verona::cpp
req_extended = reinterpret_cast<Request*>(
heap::alloc(req_count * (sizeof(Request))));
}
}

When(When&& o)
: cown_tuple(std::move(o.cown_tuple)),
f(std::forward<F>(o.f)),
is_req_extended(o.is_req_extended),
req_extended(o.req_extended)
{
o.req_extended = nullptr;
o.is_req_extended = false;
std::cout << "Will create behaviour\n";
create_behaviour_new();
}

When(When&&) = delete;
When(const When&) = delete;

~When()
Expand All @@ -439,6 +481,11 @@ namespace verona::cpp
heap::dealloc(req_extended);
}
}

BehaviourCore* get_behaviour_body()
{
return behaviour_body;
}
};

/**
Expand Down Expand Up @@ -479,12 +526,12 @@ namespace verona::cpp
{
// Execute now atomic batch makes no sense.
verona::rt::schedule_lambda(std::forward<F>(f));
return Batch(std::make_tuple());
return Batch<0>();
}
else
{
return Batch(
std::make_tuple(When(std::forward<F>(f), std::move(cown_tuple))));
return Batch<1>(
When(std::forward<F>(f), std::move(cown_tuple)).get_behaviour_body());
}
}
};
Expand All @@ -495,12 +542,6 @@ namespace verona::cpp
template<typename T>
Access(const cown_ptr<T>&) -> Access<T>;

/**
* Template deduction guide for Batch.
*/
template<typename... Args>
Batch(std::tuple<Args...>) -> Batch<Args...>;

/**
* Implements a Verona-like `when` statement.
*
Expand Down
40 changes: 40 additions & 0 deletions test/func/dynamic-atomic-sched/dynamic-atomic-sched.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright Microsoft and Project Verona Contributors.
// SPDX-License-Identifier: MIT

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

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

using namespace verona::cpp;

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

{
DynamicAtomicBatch dab;
for (int i = 0; i < 2; i++)
{
dab + (when(log) << [=](auto b) {
std::cout << "Behaviour " << i << std::endl;
});
}
}
}

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

harness.run(test_body);

return 0;
}
Loading