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

New locker mechanism and small data race fixes #1166

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
26 changes: 26 additions & 0 deletions common/wrappers/copyable-atomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include <atomic>
#include <type_traits>

namespace vk {

Expand Down Expand Up @@ -32,4 +33,29 @@ class copyable_atomic : std::atomic<T> {
// Add other operators if it is required
};

template<class T, class U = std::enable_if_t<std::is_integral_v<T>>>
struct copyable_atomic_integral : std::atomic<T> {
using std::atomic<T>::atomic;
using std::atomic<T>::operator=;
using std::atomic<T>::store;
using std::atomic<T>::load;
using std::atomic<T>::exchange;
using std::atomic<T>::operator T;
using std::atomic<T>::compare_exchange_strong;
using std::atomic<T>::compare_exchange_weak;

// integral operations
using std::atomic<T>::fetch_add;
using std::atomic<T>::fetch_sub;

copyable_atomic_integral(const copyable_atomic_integral &other) :
std::atomic<T>(other.load()) {
}

copyable_atomic_integral& operator=(copyable_atomic_integral other) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, will be better to pass by reference?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it is intentional. It handles self assignment problem properly: currently in 56 line will be used constructor number 2. In case of your suggestions there will be problem with self assignment because of another order of function (constructor in this case) overloading.

*this = other.load();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like risky. Does it potentially lead to recursive invocation of assign operator?

I suppose, will be better to rewrite

if (this != &other) { 
   this->store(other.load());
}
return *this;

Correct me, if I'm wrong.

return *this;
}
};

} // namespace vk
8 changes: 4 additions & 4 deletions compiler/compiler-core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,11 +490,11 @@ VarPtr CompilerCore::create_var(const std::string &name, VarData::Type type) {
VarPtr CompilerCore::get_global_var(const std::string &name, VertexPtr init_val) {
auto *node = globals_ht.at(vk::std_hash(name));

if (!node->data) {
{
AutoLocker<Lockable *> locker(node);
if (!node->data) {
node->data = create_var(name, VarData::var_global_t);
node->data->init_val = init_val;
node->data->init_val = init_val.clone();
node->data->is_builtin_runtime = VarData::does_name_eq_any_builtin_runtime(name);
}
}
Expand All @@ -505,11 +505,11 @@ VarPtr CompilerCore::get_global_var(const std::string &name, VertexPtr init_val)
VarPtr CompilerCore::get_constant_var(const std::string &name, VertexPtr init_val, bool *is_new_inserted) {
auto *node = constants_ht.at(vk::std_hash(name));
VarPtr new_var;
if (!node->data) {
{
AutoLocker<Lockable *> locker(node);
if (!node->data) {
new_var = create_var(name, VarData::var_const_t);
new_var->init_val = init_val;
new_var->init_val = init_val.clone();
node->data = new_var;
}
}
Expand Down
7 changes: 4 additions & 3 deletions compiler/compiler-core.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
/*** Core ***/
//Consists mostly of functions that require synchronization

#include <atomic>
#include <string>
#include <vector>

Expand Down Expand Up @@ -61,7 +62,7 @@ class CompilerCore {
std::vector<std::string> kphp_runtime_opts;
std::vector<std::string> exclude_namespaces;
bool is_untyped_rpc_tl_used{false};
bool is_functions_txt_parsed{false};
std::atomic_bool is_functions_txt_parsed{false};
function_palette::Palette function_palette;

inline bool try_require_file(SrcFilePtr file);
Expand Down Expand Up @@ -177,11 +178,11 @@ class CompilerCore {
}

void set_functions_txt_parsed() {
is_functions_txt_parsed = true;
is_functions_txt_parsed.store(true, std::memory_order_seq_cst);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use certain memory order option?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used for synchronization, where are some while(!get_functions_txt_parsed()) loops. I'm not sure that it isn't synchronized with other atomic variables.

}

bool get_functions_txt_parsed() const {
return is_functions_txt_parsed;
return is_functions_txt_parsed.load(std::memory_order_seq_cst);
}

bool is_output_mode_server() const {
Expand Down
4 changes: 2 additions & 2 deletions compiler/data/class-members.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ inline ClassMemberStaticField::ClassMemberStaticField(ClassPtr klass, VertexAdap
std::string global_var_name = replace_backslashes(klass->name) + "$$" + root->get_string();
var = G->get_global_var(global_var_name, def_val);
root->var_id = var;
var->init_val = def_val;
var->init_val = def_val.clone();
var->class_id = klass;
}

Expand Down Expand Up @@ -101,7 +101,7 @@ ClassMemberInstanceField::ClassMemberInstanceField(ClassPtr klass, VertexAdaptor
std::string local_var_name = root->get_string();
var = G->create_var(local_var_name, VarData::var_instance_t);
root->var_id = var;
var->init_val = def_val;
var->init_val = def_val.clone();
var->class_id = klass;
var->marked_as_const = klass->is_immutable || (phpdoc && phpdoc->has_tag(PhpDocType::kphp_const));
}
Expand Down
13 changes: 9 additions & 4 deletions compiler/data/var-data.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <cstdint>
#include <string>

#include "common/wrappers/copyable-atomic.h"
#include "compiler/data/class-members.h"
#include "compiler/debug.h"
#include "compiler/inferring/var-node.h"
Expand Down Expand Up @@ -45,14 +46,18 @@ class VarData {
bool marked_as_const = false;
bool is_read_only = true;
bool is_foreach_reference = false;
bool is_builtin_runtime = false; // $_SERVER, $argv, etc., see PhpScriptBuiltInSuperGlobals in runtime
int dependency_level = 0; // for constants only (c_str$, c_arr$, etc)
int offset_in_linear_mem = -1; // for globals only (offset in g_linear_mem)
int batch_idx = -1; // for constants and globals, a number [0;N), see const-globals-batched-mem.h
bool is_builtin_runtime = false; // $_SERVER, $argv, etc., see PhpScriptBuiltInSuperGlobals in runtime
vk::copyable_atomic<int> dependency_level = 0; // for constants only (c_str$, c_arr$, etc)
int offset_in_linear_mem = -1; // for globals only (offset in g_linear_mem)
int batch_idx = -1; // for constants and globals, a number [0;N), see const-globals-batched-mem.h

void set_uninited_flag(bool f);
bool get_uninited_flag();

VarData(const VarData &) = default;
VarData(VarData &&) = default;
VarData &operator=(const VarData &) = default;
VarData &operator=(VarData &&) = default;
explicit VarData(Type type);

inline Type &type() { return type_; }
Expand Down
6 changes: 4 additions & 2 deletions compiler/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,8 @@ VertexAdaptor<op_function> GenTree::get_function(bool is_lambda, const PhpDocCom
cur_function->root->cmd_ref() = VertexAdaptor<op_seq>::create();
}

auto resp = cur_function->root;

// the function is ready, register it;
// the constructor is registered later, after the entire class is parsed
if (!cur_function->is_constructor()) {
Expand All @@ -1616,10 +1618,10 @@ VertexAdaptor<op_function> GenTree::get_function(bool is_lambda, const PhpDocCom
|| cur_function->modifiers.is_instance()
|| cur_function->is_lambda()
|| kphp_required_flag;
G->register_and_require_function(cur_function, parsed_os, auto_require);
G->register_and_require_function(cur_function, parsed_os, auto_require); // pass function further
}

return cur_function->root;
return resp;
}

bool GenTree::check_seq_end() {
Expand Down
39 changes: 25 additions & 14 deletions compiler/inferring/node.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// Compiler for PHP (aka KPHP)
// Copyright (c) 2020 LLC «V Kontakte»
// Copyright (c) 2024 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#include "compiler/inferring/node.h"

#include "compiler/inferring/type-data.h"
#include "compiler/stage.h"
#include <atomic>

namespace tinf {

std::string Node::as_human_readable() const {
return type_->as_human_readable(false);
return type_.load(std::memory_order_relaxed)->as_human_readable(false);
}

void Node::register_edge_from_this(const tinf::Edge *edge) {
Expand All @@ -28,16 +28,20 @@ bool Node::try_start_recalc() {
int recalc_state_copy = recalc_state_;
int once_finished_flag = recalc_state_copy & recalc_bit_at_least_once;
switch (recalc_state_copy & 15) { // preserve bit 16 in transformations
case recalc_st_waiting:
if (__sync_bool_compare_and_swap(&recalc_state_, recalc_st_waiting | once_finished_flag, recalc_st_need_relaunch | once_finished_flag)) {
case recalc_st_waiting: {
int old = recalc_st_waiting | once_finished_flag;
if (recalc_state_.compare_exchange_strong(old, recalc_st_need_relaunch | once_finished_flag)) {
return true;
}
break;
case recalc_st_processing:
if (__sync_bool_compare_and_swap(&recalc_state_, recalc_st_processing | once_finished_flag, recalc_st_need_relaunch | once_finished_flag)) {
}
case recalc_st_processing: {
int old = recalc_st_processing | once_finished_flag;
if (recalc_state_.compare_exchange_strong(old, recalc_st_need_relaunch | once_finished_flag)) {
return false;
}
break;
}
case recalc_st_need_relaunch:
return false;
default:
Expand All @@ -49,29 +53,36 @@ bool Node::try_start_recalc() {

void Node::start_recalc() {
int once_finished_flag = recalc_state_ & recalc_bit_at_least_once; // preserve bit 16 in transformation
bool swapped = __sync_bool_compare_and_swap(&recalc_state_, recalc_st_need_relaunch | once_finished_flag, recalc_st_processing | once_finished_flag);
int old = recalc_st_need_relaunch | once_finished_flag;
bool swapped = recalc_state_.compare_exchange_strong(old, recalc_st_processing | once_finished_flag);
kphp_assert(swapped);
}

bool Node::try_finish_recalc() {
while (true) {
int recalc_state_copy = recalc_state_;
switch (recalc_state_copy) { // always set bit 16 in transformations
case recalc_st_processing:
if (__sync_bool_compare_and_swap(&recalc_state_, recalc_st_processing, recalc_st_waiting | recalc_bit_at_least_once)) {
case recalc_st_processing: {
int old = recalc_st_processing;
if (recalc_state_.compare_exchange_strong(old, recalc_st_waiting | recalc_bit_at_least_once)) {
return true;
}
break;
case recalc_st_processing | recalc_bit_at_least_once:
if (__sync_bool_compare_and_swap(&recalc_state_, recalc_st_processing | recalc_bit_at_least_once, recalc_st_waiting | recalc_bit_at_least_once)) {
}
case recalc_st_processing | recalc_bit_at_least_once: {
int old = recalc_st_processing | recalc_bit_at_least_once;
if (recalc_state_.compare_exchange_strong(old, recalc_st_waiting | recalc_bit_at_least_once)) {
return true;
}
break;
case recalc_st_need_relaunch:
if (__sync_bool_compare_and_swap(&recalc_state_, recalc_st_need_relaunch, recalc_st_need_relaunch | recalc_bit_at_least_once)) {
}
case recalc_st_need_relaunch: {
int old = recalc_st_need_relaunch;
if (recalc_state_.compare_exchange_strong(old, recalc_st_need_relaunch | recalc_bit_at_least_once)) {
return false; // false here, unlike above, but like below
}
break;
}
case recalc_st_need_relaunch | recalc_bit_at_least_once:
return false;
default:
Expand Down
16 changes: 9 additions & 7 deletions compiler/inferring/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

#pragma once

#include <atomic>
#include <string>
#include <forward_list>

#include "common/wrappers/copyable-atomic.h"
#include "compiler/debug.h"
#include "compiler/location.h"
#include "compiler/inferring/type-data.h"
#include "compiler/location.h"
#include "compiler/threading/locks.h"

namespace tinf {
Expand Down Expand Up @@ -39,11 +41,11 @@ class Node : public Lockable {
recalc_bit_at_least_once = 16,
};

const TypeData *type_{TypeData::get_type(tp_any)};
vk::copyable_atomic<const TypeData *> type_{TypeData::get_type(tp_any)};

// this field is a finite-state automation for multithreading synchronization, see enum above
// if should be placed here (after TypeData*) to make it join with the next int field in memory
int recalc_state_{recalc_st_waiting};
vk::copyable_atomic<int> recalc_state_{recalc_st_waiting};

public:

Expand All @@ -54,11 +56,11 @@ class Node : public Lockable {
std::string as_human_readable() const;

bool was_recalc_started_at_least_once() const {
return recalc_state_ > recalc_st_waiting;
return recalc_state_.load() > recalc_st_waiting;
}

bool was_recalc_finished_at_least_once() const {
return recalc_state_ >= recalc_bit_at_least_once;
return recalc_state_.load() >= recalc_bit_at_least_once;
}

void register_edge_from_this(const tinf::Edge *edge);
Expand All @@ -77,11 +79,11 @@ class Node : public Lockable {
bool try_finish_recalc();

const TypeData *get_type() const {
return type_;
return type_.load(std::memory_order_relaxed);
}

void set_type(const TypeData *type) {
type_ = type;
type_.store(type, std::memory_order_relaxed);
}

virtual void recalc(TypeInferer *inferer) = 0;
Expand Down
7 changes: 4 additions & 3 deletions compiler/inferring/type-node.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Compiler for PHP (aka KPHP)
// Copyright (c) 2020 LLC «V Kontakte»
// Copyright (c) 2024 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#include "compiler/inferring/type-node.h"
#include <atomic>

#include "compiler/inferring/type-data.h"
#include "compiler/inferring/type-node.h"
#include "compiler/stage.h"

std::string tinf::TypeNode::get_description() {
return "TypeNode at " + location_.as_human_readable() + " : " + type_->as_human_readable();
return "TypeNode at " + location_.as_human_readable() + " : " + type_.load(std::memory_order_relaxed)->as_human_readable();
}

const Location &tinf::TypeNode::get_location() const {
Expand Down
10 changes: 6 additions & 4 deletions compiler/pipes/collect-const-vars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include "compiler/pipes/collect-const-vars.h"

#include <atomic>

#include "compiler/data/src-file.h"
#include "compiler/vertex-util.h"
#include "compiler/data/var-data.h"
Expand Down Expand Up @@ -218,10 +220,10 @@ int get_expr_dep_level(VertexPtr vertex) {
}

void set_var_dep_level(VarPtr var_id) {
if (!IsComposite::visit(var_id->init_val)) {
var_id->dependency_level = 0;
} else {
var_id->dependency_level = 1 + get_expr_dep_level(var_id->init_val);
if (IsComposite::visit(var_id->init_val)) {
int old = var_id->dependency_level.load(std::memory_order_relaxed);
int cur = 1 + get_expr_dep_level(var_id->init_val);
var_id->dependency_level.store(std::max(old, cur), std::memory_order_relaxed);
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/pipes/collect-required-and-classes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class CollectRequiredPass final : public FunctionPassBase {
// avoid a race condition, when we try to search for RpcFunction.php and other built-in classes that are visible from index.php
// (if such files exist, extra src_xxx$called variables will be created: unstable codegeneration)
while (!G->get_functions_txt_parsed()) {
usleep(100000);
usleep(100000); // TODO good place for condvar
}

if (G->get_class(class_name)) {
Expand Down
4 changes: 2 additions & 2 deletions compiler/pipes/register-variables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ void RegisterVariablesPass::register_function_static_var(VertexAdaptor<op_var> v

if (default_value) {
if (!kphp_error(is_const(default_value), fmt_format("Default value of [{}] is not constant", name))) {
var->init_val = default_value;
var->init_val = default_value.clone();
}
}
var_vertex->var_id = var;
Expand All @@ -91,7 +91,7 @@ void RegisterVariablesPass::register_param_var(VertexAdaptor<op_var> var_vertex,
kphp_assert (var);
if (default_value) {
kphp_error_return(is_const(default_value) || current_function->is_extern(), fmt_format("Default value of [{}] is not constant", name));
var->init_val = default_value;
var->init_val = default_value.clone();
}
var_vertex->var_id = var;
}
Expand Down
Loading
Loading