Skip to content

Commit

Permalink
refactor: make possible to compile libcartesi without threading support
Browse files Browse the repository at this point in the history
  • Loading branch information
edubart committed Nov 14, 2023
1 parent 02cae2f commit ddc940e
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 92 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*.clang-tidy
*.deb
*.a
*.lib
*.wasm

build
Expand All @@ -28,6 +29,8 @@ doc/xml/
*.tar.gz
*.raw
*.so
*.dll
*.exe
*.dylib
*.dtb
.DS_Store
46 changes: 29 additions & 17 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ GRPC_CPP_PLUGIN=$(shell which grpc_cpp_plugin)
release?=no
sanitize?=no
coverage?=no
nothreads?=no

COVERAGE_TOOLCHAIN?=gcc
COVERAGE_OUTPUT_DIR?=coverage
Expand All @@ -50,18 +51,19 @@ endif

# Mac OS X specific setup
ifeq ($(TARGET_OS),Darwin)
PICCFLAGS=-fPIC
SOLDFLAGS=-bundle -undefined dynamic_lookup
LIBLDFLAGS=-dynamiclib -undefined dynamic_lookup
LIBLDFLAGS=-dynamiclib
EXELDFLAGS=
PTHREAD_CFLAGS=
PTHREAD_LDFLAGS=-lpthread
CC=clang
CXX=clang++
AR_EXEC=libtool -static
AR=libtool -static -o
INCS=

PTHREAD_LIB=-lpthread

ifeq ($(MACOSX_DEPLOYMENT_TARGET),)
export MACOSX_DEPLOYMENT_TARGET := $(shell sw_vers -productVersion | sed -r "s/([[:digit:]]+)\.([[:digit:]]+)\..+/\1.\2.0/")
export MACOSX_DEPLOYMENT_TARGET := $(shell sw_vers -productVersion | sed -E "s/([[:digit:]]+)\.([[:digit:]]+)\..+/\1.\2.0/")
endif

# Homebrew installation
Expand Down Expand Up @@ -95,15 +97,17 @@ PROFILE_DATA=default.profdata
else

# Linux specific setup
SOLDFLAGS=-shared -fPIC -pthread $(GCLDFLAGS)
LIBLDFLAGS=$(SOLDFLAGS) -Wl,-z,defs
PICCFLAGS=-fPIC
SOLDFLAGS=-shared $(PICCFLAGS) $(GCLDFLAGS)
LIBLDFLAGS=$(SOLDFLAGS)
EXELDFLAGS=$(GCLDFLAGS)
PTHREAD_CFLAGS=-pthread
PTHREAD_LDFLAGS=-pthread -lpthread
CC=gcc
CXX=g++
AR_EXEC=ar rcs
AR=ar rcs
INCS=

PTHREAD_LIB=-lpthread
BOOST_INC=
GRPC_PROTOBUF_INC=$(shell pkg-config --cflags-only-I grpc++ protobuf)
GRPC_PROTOBUF_LIB=$(shell pkg-config --libs grpc++ protobuf)
Expand All @@ -125,7 +129,7 @@ LUACARTESI_GRPC_LIBS=$(GRPC_PROTOBUF_LIB)
LUACARTESI_JSONRPC_LIBS=
REMOTE_CARTESI_MACHINE_LIBS=$(GRPC_PROTOBUF_LIB)
JSONRPC_REMOTE_CARTESI_MACHINE_LIBS=
TEST_MACHINE_C_API_LIBS=$(GRPC_PROTOBUF_LIB) $(PTHREAD_LIB)
TEST_MACHINE_C_API_LIBS=$(GRPC_PROTOBUF_LIB)
HASH_LIBS=

#DEFS+= -DMT_ALL_DIRTY
Expand Down Expand Up @@ -248,8 +252,16 @@ EMPTY:=
SPACE:=$(EMPTY) $(EMPTY)
CLANG_TIDY_HEADER_FILTER=$(PWD)/($(subst $(SPACE),|,$(LINTER_HEADERS)))

CXXFLAGS+=$(OPTFLAGS) -std=gnu++17 -fvisibility=hidden -fPIC -MMD $(CC_MARCH) $(INCS) $(GCFLAGS) $(UBFLAGS) $(DEFS) $(WARNS)
CFLAGS+=$(OPTFLAGS) -std=gnu99 -fvisibility=hidden -fPIC -MMD $(CC_MARCH) $(INCS) $(GCFLAGS) $(UBFLAGS) $(DEFS) $(WARNS)
ifeq ($(nothreads),no)
CFLAGS+=$(PTHREAD_CFLAGS)
CXXFLAGS+=$(PTHREAD_CFLAGS)
LDFLAGS+=$(PTHREAD_LDFLAGS)
else
DEFS+=-DNO_THREADS
endif

CXXFLAGS+=$(OPTFLAGS) -std=gnu++17 -fvisibility=hidden -MMD $(PICCFLAGS) $(CC_MARCH) $(INCS) $(GCFLAGS) $(UBFLAGS) $(DEFS) $(WARNS)
CFLAGS+=$(OPTFLAGS) -std=gnu99 -fvisibility=hidden -MMD $(PICCFLAGS) $(CC_MARCH) $(INCS) $(GCFLAGS) $(UBFLAGS) $(DEFS) $(WARNS)
LDFLAGS+=$(UBFLAGS)

COVERAGE_WORKLOAD=\
Expand All @@ -275,14 +287,14 @@ $(error invalid value for COVERAGE_TOOLCHAIN: $(COVERAGE_TOOLCHAIN))
endif
endif

CXXFLAGS+=$(MYCXXFLAGS)
CFLAGS+=$(MYCFLAGS)
CXXFLAGS+=$(MYCXXFLAGS) $(MYDEFS)
CFLAGS+=$(MYCFLAGS) $(MYDEFS)
LDFLAGS+=$(MYLDFLAGS)
SOLDFLAGS+=$(MYSOLDFLAGS)
LIBLDFLAGS+=$(MYLIBLDFLAGS)
EXELDFLAGS+=$(MYEXELDFLAGS)

all: luacartesi libcartesi.a libcartesi_jsonrpc.a grpc hash c-api jsonrpc-remote-cartesi-machine
all: libcartesi.a libcartesi_jsonrpc.a c-api luacartesi jsonrpc-remote-cartesi-machine grpc hash

.PHONY: all generate use clean test lint format format-lua check-format check-format-lua luacartesi grpc hash c-api compile_flags.txt

Expand Down Expand Up @@ -386,10 +398,10 @@ libcartesi_jsonrpc: libcartesi_jsonrpc.a $(LIBCARTESI_JSONRPC)
libcartesi_jsonrpc.so: $(LIBCARTESI_JSONRPC)

libcartesi.a: $(LIBCARTESI_OBJS)
$(AR_EXEC) $@ $(LIBCARTESI_OBJS)
$(AR) $@ $(LIBCARTESI_OBJS)

libcartesi_jsonrpc.a: $(LIBCARTESI_JSONRPC_OBJS)
$(AR_EXEC) $@ $(LIBCARTESI_JSONRPC_OBJS)
$(AR) $@ $(LIBCARTESI_JSONRPC_OBJS)

$(LIBCARTESI): $(LIBCARTESI_OBJS)
$(CXX) -o $@ $(LIBCARTESI_OBJS) $(LIBCARTESI_LIBS) $(LDFLAGS) $(LIBCARTESI_LDFLAGS) $(LIBLDFLAGS)
Expand Down
4 changes: 1 addition & 3 deletions src/json-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ std::string to_string(const char *s);

// Generate a new optional-like type
template <int I, typename T>
struct new_optional : public std::optional<T> {
using std::optional<T>::optional;
};
struct new_optional : public std::optional<T> {};

// Optional-like type used by parse_args function to identify an optional parameter
template <typename T>
Expand Down
4 changes: 1 addition & 3 deletions src/machine-c-api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include <any>
#include <cstring>
#include <exception>
#include <future>
#include <functional>
#include <ios>
#include <optional>
#include <regex>
Expand Down Expand Up @@ -66,8 +66,6 @@ int cm_result_failure(char **err_msg) try { throw; } catch (std::exception &e) {
return CM_ERROR_LENGTH_ERROR;
} catch (std::out_of_range &ex) {
return CM_ERROR_OUT_OF_RANGE;
} catch (std::future_error &ex) {
return CM_ERROR_FUTURE_ERROR;
} catch (std::logic_error &ex) {
return CM_ERROR_LOGIC_ERROR;
} catch (std::bad_optional_access &ex) {
Expand Down
1 change: 0 additions & 1 deletion src/machine-c-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ typedef enum { // NOLINT(modernize-use-using)
CM_ERROR_DOMAIN_ERROR,
CM_ERROR_LENGTH_ERROR,
CM_ERROR_OUT_OF_RANGE,
CM_ERROR_FUTURE_ERROR,
CM_ERROR_LOGIC_ERROR,
CM_LOGIC_ERROR_END,
// Bad optional access error
Expand Down
107 changes: 45 additions & 62 deletions src/machine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,11 @@
//

#include <boost/range/adaptor/sliced.hpp>
#include <chrono>
#include <cinttypes>
#include <cstdio>
#include <cstring>
#include <future>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <thread>

#include "clint-factory.h"
#include "dtb.h"
Expand Down Expand Up @@ -1448,7 +1444,7 @@ bool machine::verify_dirty_page_maps(void) const {
}

static uint64_t get_task_concurrency(uint64_t value) {
const uint64_t concurrency = value > 0 ? value : std::max(std::thread::hardware_concurrency(), 1U);
const uint64_t concurrency = value > 0 ? value : std::max(os_get_concurrency(), UINT64_C(1));
return std::min(concurrency, static_cast<uint64_t>(THREADS_MAX));
}

Expand All @@ -1467,68 +1463,55 @@ bool machine::update_merkle_tree(void) const {
// For each PMA, we launch as many threads (n) as defined on concurrency
// runtime config or as the hardware supports.
const uint64_t n = get_task_concurrency(m_r.concurrency.update_merkle_tree);
// The update_page_node_hash function in the machine_merkle_tree is not thread
// safe, so we protect it with a mutex
std::mutex updatex;
// Each thread is launched as a future, whose value tells if the
// computation succeeded
std::vector<std::future<bool>> futures;
futures.reserve(n);
for (uint64_t j = 0; j < n; ++j) {
futures.emplace_back(std::async((n == 1) ? std::launch::deferred : std::launch::async,
[&](int j) -> bool {
auto scratch = unique_calloc<unsigned char>(PMA_PAGE_SIZE, std::nothrow_t{});
if (!scratch) {
return false;
}
machine_merkle_tree::hasher_type h;
// Thread j is responsible for page i if i % n == j.
for (uint64_t i = j; i < pages_in_range; i += n) {
const uint64_t page_start_in_range = i * PMA_PAGE_SIZE;
const uint64_t page_address = pma->get_start() + page_start_in_range;
const unsigned char *page_data = nullptr;
// Skip any clean pages
if (!pma->is_page_marked_dirty(page_start_in_range)) {
continue;
}
// If the peek failed, or if it returned a page for update but
// we failed updating it, the entire process failed
if (!peek(*pma, *this, page_start_in_range, &page_data, scratch.get())) {
const bool succeeded = os_parallel_for(n, [&](int j, const parallel_for_mutex &mutex) -> bool {
auto scratch = unique_calloc<unsigned char>(PMA_PAGE_SIZE, std::nothrow_t{});
if (!scratch) {
return false;
}
machine_merkle_tree::hasher_type h;
// Thread j is responsible for page i if i % n == j.
for (uint64_t i = j; i < pages_in_range; i += n) {
const uint64_t page_start_in_range = i * PMA_PAGE_SIZE;
const uint64_t page_address = pma->get_start() + page_start_in_range;
const unsigned char *page_data = nullptr;
// Skip any clean pages
if (!pma->is_page_marked_dirty(page_start_in_range)) {
continue;
}
// If the peek failed, or if it returned a page for update but
// we failed updating it, the entire process failed
if (!peek(*pma, *this, page_start_in_range, &page_data, scratch.get())) {
return false;
}
if (page_data) {
const bool is_pristine = std::all_of(page_data, page_data + PMA_PAGE_SIZE,
[](unsigned char pp) -> bool { return pp == '\0'; });

if (is_pristine) {
// The update_page_node_hash function in the machine_merkle_tree is not thread
// safe, so we protect it with a mutex
const parallel_for_mutex_guard lock(mutex);
if (!m_t.update_page_node_hash(page_address,
machine_merkle_tree::get_pristine_hash(machine_merkle_tree::get_log2_page_size()))) {
return false;
}
if (page_data) {
const bool is_pristine = std::all_of(page_data, page_data + PMA_PAGE_SIZE,
[](unsigned char pp) -> bool { return pp == '\0'; });

if (is_pristine) {
const std::lock_guard<std::mutex> lock(updatex);
if (!m_t.update_page_node_hash(page_address,
machine_merkle_tree::get_pristine_hash(
machine_merkle_tree::get_log2_page_size()))) {
return false;
}
} else {
hash_type hash;
m_t.get_page_node_hash(h, page_data, hash);
{
const std::lock_guard<std::mutex> lock(updatex);
if (!m_t.update_page_node_hash(page_address, hash)) {
return false;
}
}
} else {
hash_type hash;
m_t.get_page_node_hash(h, page_data, hash);
{
// The update_page_node_hash function in the machine_merkle_tree is not thread
// safe, so we protect it with a mutex
const parallel_for_mutex_guard lock(mutex);
if (!m_t.update_page_node_hash(page_address, hash)) {
return false;
}
}
}
return true;
},
j));
}
// Check if any thread failed
bool succeeded = true;
for (auto &f : futures) {
succeeded = succeeded && f.get();
}
// If so, we also failed
}
}
return true;
});
// If any thread failed, we also failed
if (!succeeded) {
m_t.end_update(gh);
return false;
Expand Down
51 changes: 51 additions & 0 deletions src/os.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,17 @@
#include <iostream>
#include <string>
#include <system_error>
#include <vector>

#include "os.h"
#include "unique-c-ptr.h"

#ifdef HAVE_THREADS
#include <future>
#include <mutex>
#include <thread>
#endif

#if defined(HAVE_TTY) || defined(HAVE_MMAP) || defined(HAVE_TERMIOS) || defined(_WIN32)
#include <fcntl.h> // open
#endif
Expand All @@ -63,6 +70,14 @@

#ifdef _WIN32

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <direct.h> // mkdir
#include <io.h> // _write/_close
#include <windows.h>
Expand Down Expand Up @@ -514,4 +529,40 @@ int64_t os_now_us() {
return static_cast<int64_t>(std::chrono::duration_cast<std::chrono::microseconds>(end - start).count());
}

uint64_t os_get_concurrency() {
#ifdef HAVE_THREADS
return std::thread::hardware_concurrency();
#else
return 1;
#endif
}

bool os_parallel_for(uint64_t n, const std::function<bool(uint64_t j, const parallel_for_mutex &mutex)> &task) {
#ifdef HAVE_THREADS
if (n > 1) {
std::mutex mutex;
const parallel_for_mutex for_mutex = {[&] { mutex.lock(); }, [&] { mutex.unlock(); }};
std::vector<std::future<bool>> futures;
futures.reserve(n);
for (uint64_t j = 0; j < n; ++j) {
futures.emplace_back(std::async(std::launch::async, task, j, for_mutex));
}
// Check if any thread failed
bool succeeded = true;
for (auto &f : futures) {
succeeded = succeeded && f.get();
}
// Return overall status
return succeeded;
}
#endif
// Run without extra threads when concurrency is 1 or as fallback
const parallel_for_mutex for_mutex{[] {}, [] {}};
bool succeeded = true;
for (uint64_t j = 0; j < n; ++j) {
succeeded = succeeded && task(j, for_mutex);
}
return succeeded;
}

} // namespace cartesi
Loading

0 comments on commit ddc940e

Please sign in to comment.