Skip to content

Commit

Permalink
Runtime light: coroutine scheduler & forks (#1050)
Browse files Browse the repository at this point in the history
Main changes:
* stream management: now all operations with streams are performed through ComponentState. ComponentState stores all opened streams, releases unneeded streams, etc;
* cancellable awaitables: awaitables that can stop waiting for some event;
* forks: task_t<fork_result> is now a handle to a fork. Forks can be started, waited on, and cancelled;
* coroutine scheduler: a coroutine scheduler concept and its simple implementation are added.
  • Loading branch information
apolyakov authored Aug 2, 2024
1 parent a4d9462 commit 8ac7ded
Show file tree
Hide file tree
Showing 50 changed files with 1,325 additions and 627 deletions.
6 changes: 6 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CompileFlags:
CompilationDatabase: build/ # Search build/ directory for compile_commands.json

Diagnostics:
Suppress: cppcoreguidelines-avoid-do-while

14 changes: 12 additions & 2 deletions builtin-functions/kphp-light/functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ function get_hash_of_class (object $klass) ::: int;

function strlen ($str ::: string) ::: int;

// === Fork =======================================================================================

/** @kphp-extern-func-info interruptible cpp_template_call */
function wait(future<any> | false $id, float $timeout = -1.0) ::: ^1[*] | null;

/** @kphp-extern-func-info interruptible */
function sched_yield() ::: void;

/** @kphp-extern-func-info interruptible */
function sched_yield_sleep($timeout_ns ::: int) ::: void;

// === Rpc ========================================================================================

/** @kphp-tl-class */
Expand Down Expand Up @@ -198,8 +209,6 @@ function instance_cast(object $instance, $to_type ::: string) ::: instance<^2>;

function make_clone ($x ::: any) ::: ^1;

/** @kphp-extern-func-info interruptible */
function testyield() ::: void;
function check_shutdown() ::: void;

function warning($message ::: string) ::: void;
Expand All @@ -211,4 +220,5 @@ function debug_print_string($str ::: string) ::: void;
function byte_to_int($str ::: string) ::: ?int;
function int_to_byte($v ::: int) ::: ?string;

/** @kphp-extern-func-info interruptible */
function set_timer(int $timeout, callable():void $callback) ::: void;
7 changes: 5 additions & 2 deletions compiler/code-gen/declarations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ void FunctionDeclaration::compile(CodeGenerator &W) const {
switch (style) {
case gen_out_style::tagger:
case gen_out_style::cpp: {
if (function->is_interruptible) {
if (function->is_k2_fork) {
FunctionSignatureGenerator(W) << "task_t<fork_result> " << FunctionName(function) << "(" << params_gen << ")";
} else if (function->is_interruptible) {
FunctionSignatureGenerator(W) << "task_t<" << ret_type_gen << ">" << " " << FunctionName(function) << "(" << params_gen << ")";
} else {
FunctionSignatureGenerator(W) << ret_type_gen << " " << FunctionName(function) << "(" << params_gen << ")";
Expand Down Expand Up @@ -115,7 +117,8 @@ void FunctionParams::declare_cpp_param(CodeGenerator &W, VertexAdaptor<op_var> v
auto var_ptr = var->var_id;
if (var->ref_flag) {
W << "&";
} else if (var_ptr->marked_as_const || (!function->has_variadic_param && var_ptr->is_read_only)) {
} else if (!function->is_k2_fork && (var_ptr->marked_as_const || (!function->has_variadic_param && var_ptr->is_read_only))) {
// the top of k2 fork must take arguments by value (see C++ avoid reference parameters in coroutines)
W << (!type.type->is_primitive_type() ? "const &" : "");
}
W << VarName(var_ptr);
Expand Down
2 changes: 1 addition & 1 deletion compiler/code-gen/files/init-scripts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ void InitScriptsCpp::compile(CodeGenerator &W) const {
W << GlobalsResetFunction(main_file_id->main_function) << NL;

if (G->is_output_mode_k2_component()) {
FunctionSignatureGenerator(W) << "void init_php_scripts_in_each_worker(" << PhpMutableGlobalsRefArgument() << ", task_t<void>&run" ")" << BEGIN;
FunctionSignatureGenerator(W) << "void init_php_scripts_in_each_worker(" << PhpMutableGlobalsRefArgument() << ", task_t<void> &run" ")" << BEGIN;
} else {
FunctionSignatureGenerator(W) << "void init_php_scripts_in_each_worker(" << PhpMutableGlobalsRefArgument() << ")" << BEGIN;
}
Expand Down
14 changes: 12 additions & 2 deletions compiler/code-gen/vertex-compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,11 @@ void compile_func_call(VertexAdaptor<op_func_call> root, CodeGenerator &W, func_


if (mode == func_call_mode::fork_call) {
W << FunctionForkName(func);
if (func->is_interruptible) {
W << "(co_await start_fork_and_reschedule_t{" << FunctionName(func);
} else {
W << FunctionForkName(func);
}
} else {
if (func->is_interruptible) {
W << "(" << "co_await ";
Expand Down Expand Up @@ -874,7 +878,13 @@ void compile_func_call(VertexAdaptor<op_func_call> root, CodeGenerator &W, func_
W << JoinValues(args, ", ");
W << ")";
if (func->is_interruptible) {
W << ")";
if (mode == func_call_mode::fork_call) {
W << "})";
} else if (func->is_k2_fork) { // k2 fork's return type is 'task_t<fork_result>' so we need to unpack actual result from fork_result
W << ").get_result<" << TypeName(tinf::get_type(root)) << ">()";
} else {
W << ")";
}
}
}

Expand Down
1 change: 1 addition & 0 deletions compiler/data/function-data.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class FunctionData {
bool cpp_variadic_call = false;
bool is_resumable = false;
bool is_interruptible = false;
bool is_k2_fork = false;
bool can_be_implicitly_interrupted_by_other_resumable = false;
bool is_virtual_method = false;
bool is_overridden_method = false;
Expand Down
22 changes: 19 additions & 3 deletions compiler/pipes/calc-bad-vars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
// Copyright (c) 2020 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#include <queue>
#include "compiler/pipes/calc-bad-vars.h"

#include <queue>
#include <vector>

#include "compiler/compiler-core.h"
#include "compiler/data/class-data.h"
#include "compiler/data/src-file.h"
#include "compiler/function-pass.h"
#include "compiler/pipes/calc-func-dep.h"
#include "compiler/utils/idmap.h"

/*** Common algorithm ***/
Expand Down Expand Up @@ -548,6 +551,15 @@ class CalcBadVars {
}
}

static void calc_k2_fork(const FuncCallGraph &call_graph, const std::vector<DepData> &dep_data) {
for (int i = 0; i < call_graph.n; ++i) {
for (const auto &fork : dep_data[i].forks) {
fork->is_interruptible = true;
fork->is_k2_fork = true;
}
}
}

static void calc_resumable(const FuncCallGraph &call_graph, const std::vector<DepData> &dep_data) {
for (int i = 0; i < call_graph.n; i++) {
for (const auto &fork : dep_data[i].forks) {
Expand Down Expand Up @@ -684,8 +696,12 @@ class CalcBadVars {

{
FuncCallGraph call_graph(std::move(functions), dep_datas);
calc_interruptible(call_graph);
calc_resumable(call_graph, dep_datas);
if (G->is_output_mode_k2_component()) {
calc_k2_fork(call_graph, dep_datas);
calc_interruptible(call_graph);
} else {
calc_resumable(call_graph, dep_datas);
}
generate_bad_vars(call_graph, dep_datas);
check_func_colors(call_graph);
save_func_dep(call_graph);
Expand Down
13 changes: 13 additions & 0 deletions runtime-core/core-types/decl/optional.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,16 @@ using enable_if_t_is_optional_t2 = std::enable_if_t<std::is_same<T, Optional<T2>

template<class T>
using enable_if_t_is_optional_string = enable_if_t_is_optional_t2<T, string>;

template<typename T>
struct InternalOptionalType {
using type = T;
};

template<typename T>
struct InternalOptionalType<Optional<T>> {
using type = T;
};

template<typename T>
using internal_optional_type_t = typename InternalOptionalType<T>::type;
23 changes: 13 additions & 10 deletions runtime-core/memory-resource/resource_allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#pragma once

#include <functional>
#include <map>
#include <queue>
#include <unordered_map>
#include <unordered_set>

#include "common/wrappers/likely.h"

Expand All @@ -19,19 +22,16 @@ class resource_allocator {
using value_type = T;

template<class, class>
friend
class resource_allocator;
friend class resource_allocator;

explicit resource_allocator(MemoryResource &memory_resource) noexcept:
memory_resource_(memory_resource) {
}
explicit resource_allocator(MemoryResource &memory_resource) noexcept
: memory_resource_(memory_resource) {}

template<class U>
explicit resource_allocator(const resource_allocator<U, MemoryResource> &other) noexcept:
memory_resource_(other.memory_resource_) {
}
explicit resource_allocator(const resource_allocator<U, MemoryResource> &other) noexcept
: memory_resource_(other.memory_resource_) {}

value_type *allocate(size_t size, void const * = nullptr) {
value_type *allocate(size_t size, [[maybe_unused]] void const *ptr = nullptr) {
static_assert(sizeof(value_type) <= max_value_type_size(), "memory limit");
auto result = static_cast<value_type *>(memory_resource_.allocate(sizeof(value_type) * size));
if (unlikely(!result)) {
Expand All @@ -46,7 +46,7 @@ class resource_allocator {
}

static constexpr size_t max_value_type_size() {
return 128u;
return 128U;
}

friend inline bool operator==(const resource_allocator &lhs, const resource_allocator &rhs) noexcept {
Expand All @@ -65,6 +65,9 @@ namespace stl {
template<class Key, class T, class Resource, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>>
using unordered_map = std::unordered_map<Key, T, Hash, KeyEqual, resource_allocator<std::pair<const Key, T>, Resource>>;

template<class T, class Resource, class Hash = std::hash<T>, class KeyEqual = std::equal_to<T>>
using unordered_set = std::unordered_set<T, Hash, KeyEqual, resource_allocator<T, Resource>>;

template<class Key, class Value, class Resource, class Cmp = std::less<Key>>
using map = std::map<Key, Value, Cmp, resource_allocator<std::pair<const Key, Value>, Resource>>;

Expand Down
47 changes: 47 additions & 0 deletions runtime-core/utils/small-object-storage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Compiler for PHP (aka KPHP)
// Copyright (c) 2020 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#pragma once

#include <array>
#include <cstddef>
#include <type_traits>
#include <utility>

#include "runtime-core/runtime-core.h"

template<size_t limit>
union small_object_storage {
std::array<std::byte, limit> storage_;
void *storage_ptr;

template<typename T, typename... Args>
std::enable_if_t<sizeof(T) <= limit, T *> emplace(Args &&...args) noexcept {
return new (storage_.data()) T(std::forward<Args>(args)...);
}
template<typename T>
std::enable_if_t<sizeof(T) <= limit, T *> get() noexcept {
return reinterpret_cast<T *>(storage_.data());
}
template<typename T>
std::enable_if_t<sizeof(T) <= limit> destroy() noexcept {
get<T>()->~T();
}

template<typename T, typename... Args>
std::enable_if_t < limit<sizeof(T), T *> emplace(Args &&...args) noexcept {
storage_ptr = RuntimeAllocator::current().alloc_script_memory(sizeof(T));
return new (storage_ptr) T(std::forward<Args>(args)...);
}
template<typename T>
std::enable_if_t < limit<sizeof(T), T *> get() noexcept {
return static_cast<T *>(storage_ptr);
}
template<typename T>
std::enable_if_t < limit<sizeof(T)> destroy() noexcept {
T *mem = get<T>();
mem->~T();
RuntimeAllocator::current().free_script_memory(mem, sizeof(T));
}
};
Loading

0 comments on commit 8ac7ded

Please sign in to comment.