Skip to content

Commit e812adc

Browse files
committed
Language/MetaCircularVirtualMachine: Adding multithreading bytecode instructions
1 parent cf033fd commit e812adc

2 files changed

Lines changed: 262 additions & 17 deletions

File tree

modules/Language/MetaCircularVirtualMachine.mpp

Lines changed: 203 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ export module CppUtils.Language.MetaCircularVirtualMachine;
22

33
import std;
44
import CppUtils.Container.MeshNetwork;
5+
import CppUtils.Container.SafeShared;
6+
import CppUtils.Execution.Event;
57
import CppUtils.Execution.ScopeGuard;
68
import CppUtils.Language.AST;
79
import CppUtils.String;
10+
import CppUtils.Thread.SharedLocker;
811
import CppUtils.Thread.ThreadPool;
912
import CppUtils.Type.Specialization;
1013

@@ -88,6 +91,21 @@ export namespace CppUtils::Language
8891
std::expected<MeshNodePtr, std::string_view> value;
8992
};
9093

94+
struct Environment final
95+
{
96+
Thread::SharedLocker<std::unordered_map<Type::Token, std::function<StepResult(MetaCircularVirtualMachine&, MeshNodePtr)>>> functions;
97+
Thread::ThreadPool threadPool;
98+
Thread::SharedLocker<std::unordered_map<std::size_t, std::shared_ptr<std::future<void>>>> threads;
99+
Thread::SharedLocker<std::unordered_map<std::size_t, std::shared_ptr<Execution::Event>>> events;
100+
std::atomic<std::size_t> nextThreadId{0};
101+
std::atomic<std::size_t> nextEventId{0};
102+
Thread::SharedLocker<std::string> firstErrorMessage;
103+
std::atomic<bool> stopRequested{false};
104+
};
105+
106+
MeshNodePtr context = MeshNodePtr::makeRoot(0uz);
107+
std::shared_ptr<Environment> environment;
108+
91109
[[nodiscard]] static inline auto next(MeshNodePtr instruction) noexcept -> MeshNodePtr
92110
{
93111
using namespace String::Literals;
@@ -96,18 +114,15 @@ export namespace CppUtils::Language
96114
.value_or(MeshNodePtr{});
97115
}
98116

99-
MeshNodePtr context = MeshNodePtr::makeRoot(0uz);
100-
std::unordered_map<Type::Token, std::function<StepResult(MetaCircularVirtualMachine&, MeshNodePtr)>> functions;
101-
Thread::ThreadPool threadPool;
102-
103117
template<class T = MetaCircularVirtualMachine>
104118
inline auto addFunction(Type::Token token, auto&& function) -> void
105119
{
106120
using namespace String::Literals;
107121

108-
if (not functions.contains(token))
122+
auto functions = environment->functions.uniqueAccess();
123+
if (not functions.value().contains(token))
109124
{
110-
functions[token] = [function = std::forward<decltype(function)>(function)](MetaCircularVirtualMachine& interpreter, MeshNodePtr instruction) -> StepResult {
125+
functions.value()[token] = [function = std::forward<decltype(function)>(function)](MetaCircularVirtualMachine& interpreter, MeshNodePtr instruction) -> StepResult {
111126
return StepResult{function(static_cast<T&>(interpreter), instruction)};
112127
};
113128
if (not context.contains(token))
@@ -145,11 +160,35 @@ export namespace CppUtils::Language
145160
};
146161
}
147162

148-
inline MetaCircularVirtualMachine()
163+
inline explicit MetaCircularVirtualMachine(std::shared_ptr<Environment> environment):
164+
environment{std::move(environment)}
165+
{}
166+
167+
inline MetaCircularVirtualMachine():
168+
environment{std::make_shared<Environment>()}
149169
{
150170
using namespace std::literals;
151171
using namespace String::Literals;
152172

173+
environment->threadPool.setOnError([environment = environment](std::exception_ptr exceptionPointer) {
174+
if (environment->stopRequested.exchange(true))
175+
return;
176+
177+
try
178+
{
179+
if (exceptionPointer)
180+
std::rethrow_exception(exceptionPointer);
181+
}
182+
catch (const std::exception& exception)
183+
{
184+
environment->firstErrorMessage.uniqueAccess().value() = exception.what();
185+
}
186+
catch (...)
187+
{
188+
environment->firstErrorMessage.uniqueAccess().value() = "Unknown background error";
189+
}
190+
});
191+
153192
addFunction("define"_token, [](MetaCircularVirtualMachine&, MeshNodePtr instruction) -> std::expected<MeshNodePtr, std::string_view> {
154193
return instruction.at("value"_token)
155194
.and_then([](auto valueBranch) { return valueBranch.back(); })
@@ -238,10 +277,146 @@ export namespace CppUtils::Language
238277
addFunction("/"_token, arithmeticOperation<"/">(std::divides<>{}));
239278
addFunction("%"_token, arithmeticOperation<"%">(std::modulus<>{}));
240279
addFunction("<"_token, arithmeticOperation<"<">(std::less<>{}));
280+
281+
addFunction("thread"_token, [](MetaCircularVirtualMachine& interpreter, MeshNodePtr instruction) -> std::expected<MeshNodePtr, std::string_view> {
282+
auto targetInstruction = instruction.at("target"_token).and_then([](auto branch) { return branch.front(); });
283+
if (not targetInstruction)
284+
return std::unexpected{"thread: Missing target"sv};
285+
286+
auto threadContext = MeshNodePtr::makeRoot("threadContext"_token);
287+
threadContext["scope"_token] >> interpreter.context;
288+
289+
auto task = [target = targetInstruction.value(),
290+
threadContext = std::move(threadContext),
291+
environment = interpreter.environment]() mutable {
292+
auto threadInterpreter = MetaCircularVirtualMachine{std::move(environment)};
293+
threadInterpreter.context = std::move(threadContext);
294+
295+
if (auto result = threadInterpreter(target); not result)
296+
throw std::runtime_error{std::string{result.error()}};
297+
};
298+
299+
auto id = interpreter.environment->nextThreadId++;
300+
interpreter.environment->threads.uniqueAccess().value()[id] = std::make_shared<std::future<void>>(interpreter.environment->threadPool.call(task));
301+
302+
auto resultNode = instruction.at("result"_token).and_then([](auto branch) { return branch.front(); });
303+
if (not resultNode)
304+
return std::unexpected{"thread: Missing result node"sv};
305+
306+
resultNode->setValue(id);
307+
308+
return next(instruction);
309+
});
310+
311+
addFunction("join"_token, [](MetaCircularVirtualMachine& interpreter, MeshNodePtr instruction) -> std::expected<MeshNodePtr, std::string_view> {
312+
auto idNode = instruction.at("thread"_token).and_then([](auto branch) { return branch.front(); });
313+
if (not idNode)
314+
return std::unexpected{"join: Missing thread id"sv};
315+
316+
auto id = idNode->getValue().value();
317+
auto future = [&]() -> std::shared_ptr<std::future<void>> {
318+
auto threads = interpreter.environment->threads.sharedAccess();
319+
if (auto it = threads.value().find(id); it != std::end(threads.value()))
320+
return it->second;
321+
return nullptr;
322+
}();
323+
324+
if (future and future->valid())
325+
future->wait();
326+
327+
{
328+
auto threads = interpreter.environment->threads.uniqueAccess();
329+
threads.value().erase(id);
330+
}
331+
332+
return next(instruction);
333+
});
334+
335+
addFunction("detach"_token, [](MetaCircularVirtualMachine& interpreter, MeshNodePtr instruction) -> std::expected<MeshNodePtr, std::string_view> {
336+
auto idNode = instruction.at("thread"_token).and_then([](auto branch) { return branch.front(); });
337+
if (not idNode)
338+
return std::unexpected{"detach: Missing thread id"sv};
339+
340+
auto id = idNode->getValue().value();
341+
auto threads = interpreter.environment->threads.uniqueAccess();
342+
threads.value().erase(id);
343+
344+
return next(instruction);
345+
});
346+
347+
addFunction("event"_token, [](MetaCircularVirtualMachine& interpreter, MeshNodePtr instruction) -> std::expected<MeshNodePtr, std::string_view> {
348+
auto resultNode = instruction.at("result"_token).and_then([](auto branch) { return branch.front(); });
349+
if (not resultNode)
350+
return std::unexpected{"event: Missing result node"sv};
351+
352+
auto id = interpreter.environment->nextEventId++;
353+
interpreter.environment->events.uniqueAccess().value()[id] = std::make_shared<Execution::Event>();
354+
355+
resultNode->setValue(id);
356+
357+
return next(instruction);
358+
});
359+
360+
addFunction("wait"_token, [](MetaCircularVirtualMachine& interpreter, MeshNodePtr instruction) -> std::expected<MeshNodePtr, std::string_view> {
361+
auto idNode = instruction.at("event"_token).and_then([](auto branch) { return branch.front(); });
362+
if (not idNode)
363+
return std::unexpected{"wait: Missing event id"sv};
364+
365+
auto id = idNode->getValue().value();
366+
auto event = [&]() -> std::shared_ptr<Execution::Event> {
367+
auto events = interpreter.environment->events.sharedAccess();
368+
if (auto it = events.value().find(id); it != std::end(events.value()))
369+
return it->second;
370+
return nullptr;
371+
}();
372+
373+
if (event)
374+
event->wait();
375+
376+
return next(instruction);
377+
});
378+
379+
addFunction("notify"_token, [](MetaCircularVirtualMachine& interpreter, MeshNodePtr instruction) -> std::expected<MeshNodePtr, std::string_view> {
380+
auto idNode = instruction.at("event"_token).and_then([](auto branch) { return branch.front(); });
381+
if (not idNode)
382+
return std::unexpected{"notify: Missing event id"sv};
383+
384+
auto id = idNode->getValue().value();
385+
auto event = [&]() -> std::shared_ptr<Execution::Event> {
386+
auto events = interpreter.environment->events.sharedAccess();
387+
if (auto it = events.value().find(id); it != std::end(events.value()))
388+
return it->second;
389+
return nullptr;
390+
}();
391+
392+
if (event)
393+
event->notify();
394+
395+
return next(instruction);
396+
});
241397
}
242398

243399
virtual ~MetaCircularVirtualMachine() = default;
244400

401+
[[nodiscard]] inline auto reportError(std::string_view message) const -> std::expected<void, std::string_view>
402+
{
403+
if (not environment->stopRequested.exchange(true))
404+
environment->firstErrorMessage.uniqueAccess().value() = message;
405+
406+
return resultWithBackgroundErrors(std::unexpected{message});
407+
}
408+
409+
[[nodiscard]] inline auto resultWithBackgroundErrors(std::expected<void, std::string_view> result) const -> std::expected<void, std::string_view>
410+
{
411+
if (not environment->stopRequested.load())
412+
return result;
413+
414+
if (auto firstError = environment->firstErrorMessage.sharedAccess(); not std::empty(firstError.value()))
415+
return std::unexpected{std::string_view{firstError.value()}};
416+
417+
return result;
418+
}
419+
245420
[[nodiscard]] inline auto get(Type::Token token) -> std::expected<MeshNodePtr, std::string_view>
246421
{
247422
using namespace std::literals;
@@ -260,7 +435,7 @@ export namespace CppUtils::Language
260435
{
261436
using namespace String::Literals;
262437

263-
while (instruction)
438+
while (instruction and not environment->stopRequested.load())
264439
{
265440
auto definition = instruction;
266441
while (true)
@@ -276,18 +451,24 @@ export namespace CppUtils::Language
276451
}
277452

278453
auto definitionToken = definition.getValue().value();
279-
if (auto functionIt = functions.find(definitionToken); functionIt != std::cend(functions))
280-
if (auto result = functionIt->second(*this, instruction).value)
454+
455+
if (auto function = [&]() -> std::function<StepResult(MetaCircularVirtualMachine&, MeshNodePtr)> {
456+
auto functions = environment->functions.sharedAccess();
457+
if (auto functionIt = functions.value().find(definitionToken); functionIt != std::cend(functions.value()))
458+
return functionIt->second;
459+
return nullptr;
460+
}())
461+
if (auto result = function(*this, instruction).value)
281462
instruction = result.value();
282463
else
283-
return std::unexpected{result.error()};
464+
return reportError(result.error());
284465
else if (auto branch = definition.at("next"_token))
285466
instruction = branch.value().front().value_or(MeshNodePtr{});
286467
else
287468
break;
288469
}
289470

290-
return {};
471+
return resultWithBackgroundErrors({});
291472
}
292473

293474
[[nodiscard]] inline auto operator()(Type::Token token) -> std::expected<void, std::string_view>
@@ -297,15 +478,20 @@ export namespace CppUtils::Language
297478
if (auto instruction = get(token))
298479
return (*this)(instruction.value());
299480

300-
if (auto functionIt = functions.find(token); functionIt != std::cend(functions))
481+
if (auto function = [&]() -> std::function<StepResult(MetaCircularVirtualMachine&, MeshNodePtr)> {
482+
auto functions = environment->functions.sharedAccess();
483+
if (auto functionIt = functions.value().find(token); functionIt != std::cend(functions.value()))
484+
return functionIt->second;
485+
return nullptr;
486+
}())
301487
{
302-
if (auto result = functionIt->second(*this, MeshNodePtr::make(token)).value)
303-
return (*this)(result.value());
488+
if (auto result = function(*this, MeshNodePtr::make(token)).value)
489+
return resultWithBackgroundErrors((*this)(result.value()));
304490
else
305-
return std::unexpected{result.error()};
491+
return reportError(result.error());
306492
}
307493

308-
return std::unexpected{"Unknown function"sv};
494+
return reportError("Unknown function"sv);
309495
}
310496
};
311497
}

tests/Language/MetaCircularVirtualMachine.mpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,5 +344,64 @@ namespace CppUtils::UnitTest::Language::MetaCircularVirtualMachine
344344
suite.expectEqual(result.getValue().value(), false);
345345
}
346346
});
347+
348+
suite.addTest("Multithreading - thread and join", [&] {
349+
auto interpreter = MetaCircularVirtualMachine{};
350+
auto value = MeshNodePtr::make(0uz);
351+
352+
auto threadTarget = MeshNodePtr::makeRoot("define"_token);
353+
threadTarget["value"_token] >> MeshNodePtr::make(42uz);
354+
threadTarget["target"_token] >> value;
355+
356+
auto threadIdNode = MeshNodePtr::make(0uz);
357+
358+
auto instruction = MeshNodePtr::makeRoot("thread"_token);
359+
instruction["target"_token] >> threadTarget;
360+
instruction["result"_token] >> threadIdNode;
361+
362+
auto joinInstruction = instruction["next"_token] >> MeshNodePtr::make("join"_token);
363+
joinInstruction["thread"_token] >> threadIdNode;
364+
365+
const auto status = interpreter(instruction);
366+
if (not status)
367+
Logger::print<"error">("Error: {}", status.error());
368+
369+
suite.expect(status);
370+
suite.expectEqual(value.getValue().value(), 42uz);
371+
});
372+
373+
suite.addTest("Multithreading - event, wait, notify", [&] {
374+
auto interpreter = MetaCircularVirtualMachine{};
375+
auto value = MeshNodePtr::make(0uz);
376+
auto eventIdNode = MeshNodePtr::make(0uz);
377+
auto threadIdNode = MeshNodePtr::make(0uz);
378+
379+
auto instruction = MeshNodePtr::makeRoot("event"_token);
380+
instruction["result"_token] >> eventIdNode;
381+
382+
auto threadTarget = MeshNodePtr::makeRoot("wait"_token);
383+
threadTarget["event"_token] >> eventIdNode;
384+
385+
auto threadDefine = threadTarget["next"_token] >> MeshNodePtr::make("define"_token);
386+
threadDefine["value"_token] >> MeshNodePtr::make(42uz);
387+
threadDefine["target"_token] >> value;
388+
389+
auto threadInstruction = instruction["next"_token] >> MeshNodePtr::make("thread"_token);
390+
threadInstruction["target"_token] >> threadTarget;
391+
threadInstruction["result"_token] >> threadIdNode;
392+
393+
auto notifyInstruction = threadInstruction["next"_token] >> MeshNodePtr::make("notify"_token);
394+
notifyInstruction["event"_token] >> eventIdNode;
395+
396+
auto joinInstruction = notifyInstruction["next"_token] >> MeshNodePtr::make("join"_token);
397+
joinInstruction["thread"_token] >> threadIdNode;
398+
399+
const auto status = interpreter(instruction);
400+
if (not status)
401+
Logger::print<"error">("Error: {}", status.error());
402+
403+
suite.expect(status);
404+
suite.expectEqual(value.getValue().value(), 42uz);
405+
});
347406
}};
348407
}

0 commit comments

Comments
 (0)