Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
80fea32
sketch
Mar 29, 2026
3acc32a
spell
Apr 9, 2026
28a7d5c
await suspend
Apr 10, 2026
73241e9
in final suspend
Apr 10, 2026
22c6a4d
todo
Apr 10, 2026
2ea7821
root declares what it is
Apr 10, 2026
cb49034
final suspend loop on cancel
Apr 10, 2026
5f67646
restore assert
Apr 10, 2026
a2af5da
remove pre-suspend check
Apr 10, 2026
81fb23b
owning parent
Apr 10, 2026
577bfec
return adr abstraction
Apr 10, 2026
80869f4
Revert "owning parent"
Apr 10, 2026
d7a175f
extra template param in pkg
Apr 10, 2026
d784a41
op overloads
Apr 10, 2026
9facc5c
check return is non null
Apr 10, 2026
194101d
todo
Apr 10, 2026
3926434
cancel propagation
Apr 10, 2026
b3ec305
PERF: move propagation to await_transform
Apr 10, 2026
37f99b5
PERF: move check
Apr 10, 2026
33a599f
Revert "PERF: move check"
Apr 10, 2026
7d5c1de
Revert "PERF: move propagation to await_transform"
Apr 10, 2026
1fda32b
split final suspend
Apr 10, 2026
953975b
Revert "remove pre-suspend check"
Apr 10, 2026
f9dac31
Reapply "remove pre-suspend check"
Apr 10, 2026
20d76d4
drop inline forcing
Apr 10, 2026
d38389b
revert owner split release
Apr 10, 2026
c80f7d7
unranched
Apr 10, 2026
4327a2b
Revert "unranched"
Apr 10, 2026
13b3f63
unique types in pkg
Apr 10, 2026
a5ca406
comments
Apr 15, 2026
97bbd71
better names
Apr 15, 2026
f53b1a0
rm comments
Apr 15, 2026
fe90470
full pre-release
Apr 15, 2026
764b6d8
full fused release
Apr 15, 2026
767eb38
Revert "full fused release"
Apr 15, 2026
12bb9c5
double branch reduced
Apr 15, 2026
da7054b
Revert "double branch reduced"
Apr 15, 2026
9fc5baa
fixup negation
Apr 15, 2026
3e21509
fixup todo
Apr 15, 2026
2817984
refactor names of final suspend parts
Apr 15, 2026
acc9a7a
set cancellation in await suspend
Apr 15, 2026
20141ee
unconditional cancel test
Apr 15, 2026
fcc1924
bad
Apr 15, 2026
2054432
Revert "bad"
Apr 15, 2026
2e56de7
Revert "unconditional cancel test"
Apr 15, 2026
a5e8f28
rm comment
Apr 15, 2026
6183c6e
constify
Apr 15, 2026
774ed44
add todo
Apr 15, 2026
20c26f0
add todo
Apr 15, 2026
30db539
cancel test
Apr 10, 2026
5d21d85
join cancel fix
Apr 17, 2026
1a9dafd
cancel fuzz
Apr 16, 2026
dd5f712
fix conditional
Apr 16, 2026
936d2c5
agents tweaks
Apr 16, 2026
84e7d5e
Revert "cancel fuzz"
Apr 16, 2026
19dbf7a
todo
Apr 16, 2026
696dcfb
first pass
Apr 17, 2026
5eeb0f2
second pass
Apr 17, 2026
6d97e26
touchup inculdes
Apr 17, 2026
c983c1d
drop C from bench
Apr 17, 2026
9a115c3
no fixed import
Apr 17, 2026
ef30fb2
all use full imports
Apr 17, 2026
6413dfc
tidy up cmakelists
Apr 17, 2026
bc609d7
alternative handling of cancel at join
Apr 17, 2026
947e203
further refine cancel path
Apr 17, 2026
a9f1be5
tmp
Apr 17, 2026
3f5c913
not all threads
Apr 17, 2026
0f86acc
don't use internal
Apr 17, 2026
339c5f0
complexity
Apr 17, 2026
87c992b
Revert "cancel test"
Apr 17, 2026
7e9e515
structure for stop.cxx
Apr 17, 2026
4746555
stop.cxx
Apr 17, 2026
0b20224
use stop.cxx
Apr 17, 2026
dea4509
better op names
Apr 17, 2026
571e2ca
todo
Apr 17, 2026
e679bf4
cancel tests
Apr 17, 2026
777e0e5
root package propagates exception
Apr 17, 2026
fe1edf1
stop plumbed
Apr 17, 2026
ecb1c24
fix infinite loop
Apr 17, 2026
c846cbc
access the stop source of the reciver
Apr 17, 2026
b84d13d
todo
Apr 17, 2026
e1e1ad8
allow early cancellation of root
Apr 17, 2026
37e763b
tmp cancel
Apr 17, 2026
632952e
co_await scope()
Apr 17, 2026
60b1635
no export stop source
Apr 17, 2026
75ef6ca
explicitly default initialize
Apr 17, 2026
258f325
basic cancel test
Apr 17, 2026
fab63ff
busy versions
Apr 17, 2026
1216892
tests
Apr 17, 2026
3b9bd7a
TMP no bind
Apr 17, 2026
7f9df1d
clean ups
Apr 18, 2026
d10808f
cancel notes
Apr 18, 2026
e7212c0
Revert "TMP no bind"
Apr 18, 2026
2d6d98b
move external to benchmark
Apr 18, 2026
82a5312
stop token
Apr 19, 2026
1c42326
use stop token
Apr 19, 2026
57bedec
split ops
Apr 19, 2026
8272469
markdown (delete before merge)
Apr 19, 2026
5ae91df
fix promise
Apr 19, 2026
d2ff9b3
fix tests
Apr 19, 2026
31bfee8
reciever changes
Apr 19, 2026
80f4e0d
todo
Apr 19, 2026
5161847
use scoped join
Apr 19, 2026
6764b4e
format
Apr 19, 2026
07f6f97
rename 1
Apr 19, 2026
56bbc33
rename 2
Apr 19, 2026
7cce296
rename 3
Apr 19, 2026
282f9a2
rename 4
Apr 19, 2026
a68264d
rename 5
Apr 19, 2026
f4cc3bc
rm dead file
Apr 19, 2026
9a2b0ac
rename 6
Apr 19, 2026
e077b35
rename 7
Apr 19, 2026
1581ec1
rename 8
Apr 19, 2026
c957159
restore comment
Apr 19, 2026
a957970
nicer name
Apr 19, 2026
bdb3d4e
complexity notes
Apr 19, 2026
06ba810
more tests
Apr 19, 2026
aa4af3f
dynamic section
Apr 19, 2026
37dc76e
rename
Apr 19, 2026
1759e34
gaurd max
Apr 19, 2026
6dd0f41
update comments
Apr 19, 2026
d42e9fb
drop branch
Apr 19, 2026
4e42502
drop template
Apr 19, 2026
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
2 changes: 1 addition & 1 deletion .codespellrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[codespell]
builtin = clear,rare,en-GB_to_en-US,names,informal,code
builtin = clear,rare,names,informal,code
check-filenames =
check-hidden =
ignore-words-list = deque,warmup,stdio,copyable,combinate
Expand Down
40 changes: 1 addition & 39 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,34 +103,6 @@ All tests should pass. If tests fail, check that:
- Build completed without errors
- Any changes you have made are correct

## Linting & Validation

The CI runs two linting tools that you should run before committing:

### codespell (spelling)

```bash
codespell
```

Config: `.codespellrc` (ignores: build/, .git/, etc.)
Should produce no output if passing.

### clang-format (code formatting)

```bash
find src include test benchmark/src -name "*.cpp" -o -name "*.hpp" -o -name "*.cxx" | xargs clang-format --dry-run --Werror
```

Config: `.clang-format` (110 column limit, specific style)
Should produce no output if passing.

**To auto-fix formatting**:

```bash
find src include test benchmark/src -name "*.cpp" -o -name "*.hpp" -o -name "*.cxx" | xargs clang-format -i
```

## Project Structure

### Source Layout
Expand All @@ -150,10 +122,9 @@ libfork/
│ ├── batteries/ # libfork.batteries — stacks, contexts, adaptors
│ │ ├── batteries.cxx # aggregator
│ │ └── *.cxx # :partitions
── schedulers/ # libfork.schedulers — concrete schedulers
── schedulers/ # libfork.schedulers — concrete schedulers
│ │ ├── schedulers.cxx # aggregator
│ │ └── *.cxx # :partitions
│ └── exception.cpp # terminate_with() implementation
├── test/src/**/ # Test suite (Catch2) — uses `import libfork;`
│ └── *.cpp
├── benchmark/src/ # Benchmarking suite (google-benchmark)
Expand Down Expand Up @@ -189,7 +160,6 @@ All workflows follow this pattern:
1. **Modify source files** in `src/`, `include/`, `test/`, or `benchmark/`
2. **Rebuild**: `cmake --build --preset <your-preset>`
3. **Test**: `ctest --preset <your-preset>`
4. **Lint**: Run codespell and clang-format checks

#### Adding/removing files from `src/` or `include/`

Expand Down Expand Up @@ -230,11 +200,3 @@ rm -rf build/

**Problem**: "Could not automatically find libc++.modules.json"
**Solution**: Ensure LLVM is installed via Homebrew; toolchain auto-detects the path

### Linting Failures

**Problem**: clang-format errors
**Solution**: Run fix command above to auto-format code

**Problem**: codespell errors
**Solution**: Fix typos or add to ignore list in `.codespellrc` if false positive
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ set(CMAKE_CXX_MODULE_STD 1)
add_library(libfork_libfork)
add_library(libfork::libfork ALIAS libfork_libfork)

# target_link_libraries(libfork_libfork PRIVATE Threads::Threads)
target_link_libraries(libfork_libfork PUBLIC Threads::Threads)

set_property(TARGET libfork_libfork PROPERTY EXPORT_NAME libfork)

Expand Down Expand Up @@ -84,6 +84,7 @@ target_sources(libfork_libfork
src/core/execute.cxx
src/core/receiver.cxx
src/core/promise.cxx
src/core/stop.cxx
# libfork.batteries
src/batteries/batteries.cxx
src/batteries/deque.cxx
Expand Down
12 changes: 4 additions & 8 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 4.2.1 FATAL_ERROR)

project(libfork_benchmark LANGUAGES C CXX)
project(libfork_benchmark LANGUAGES CXX)

if(NOT CMAKE_BUILD_TYPE STREQUAL "Release")
message(WARNING "It is recommended to build benchmarks in Release mode for accurate results.")
Expand All @@ -17,7 +17,8 @@ target_link_libraries(libfork_benchmark
benchmark::benchmark_main
)

# Common headers
# Common components

target_sources(libfork_benchmark
PRIVATE
FILE_SET HEADERS FILES
Expand All @@ -28,18 +29,13 @@ target_sources(libfork_benchmark
src
)

# Common sources
target_sources(libfork_benchmark
PRIVATE
src/libfork_benchmark/uts/uts.cpp
)


# C lib for UTS
add_library(uts_c OBJECT
src/libfork_benchmark/uts/external/uts.c
src/libfork_benchmark/uts/external/rng/brg_sha1.c
)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/uts external/uts)

target_link_libraries(libfork_benchmark PRIVATE uts_c)

Expand Down
19 changes: 19 additions & 0 deletions benchmark/external/uts/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 4.2.1 FATAL_ERROR)

project(uts_external LANGUAGES C)

add_library(uts_c)

target_sources(uts_c
PRIVATE
src/uts.c
src/rng/brg_sha1.c
PUBLIC
FILE_SET HEADERS
BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include
FILES
include/uts/uts.h
include/uts/rng/rng.h
include/uts/rng/brg_sha1.h
include/uts/rng/brg_types.h
)
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
#ifndef _SHA1_H
#define _SHA1_H

#include "brg_types.h"
#include "uts/rng/brg_types.h"

#define SHA1_BLOCK_SIZE 64
#define SHA1_DIGEST_SIZE 20
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#ifndef _RNG_H
#define _RNG_H

#include "brg_sha1.h"
#include "uts/rng/brg_sha1.h"

#endif /* _RNG_H */
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
extern "C" {
#endif

#include "rng/rng.h"
#include "uts/rng/rng.h"

#define UTS_VERSION "2.1"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
#include <string.h> /* for memcpy() etc. */

#include "brg_endian.h"
#include "brg_sha1.h"
#include "uts/rng/brg_sha1.h"

#if defined(__cplusplus)
extern "C" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#include <string.h>
#include <time.h>

#include "uts.h"
#include "uts/uts.h"

/***********************************************************
* tree generation and search parameters *
Expand Down
10 changes: 9 additions & 1 deletion benchmark/src/libfork_benchmark/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ struct incorrect_result : public std::runtime_error {
using std::runtime_error::runtime_error;
};

inline constexpr unsigned bench_max_threads = 12;
inline void bench_thread_args(benchmark::Benchmark *bench, auto make_args) {
unsigned hw = std::max(1U, std::thread::hardware_concurrency());
for (unsigned t : {1U, 2U, 4U, 6U, 8U, 12U, 16U, 24U, 32U, 48U, 64U, 96U}) {
if (t > hw) {
return;
}
make_args(bench, t);
}
}
Comment thread
ConorWilliams marked this conversation as resolved.

#define CHECK_RESULT(result, expected) \
do { \
Expand Down
16 changes: 10 additions & 6 deletions benchmark/src/libfork_benchmark/fib/libfork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ struct fib {
std::int64_t lhs = 0;
std::int64_t rhs = 0;

using scope = lf::scope<Context>;
auto sc = co_await lf::scope();

co_await scope::fork(&rhs, fib{}, n - 2);
co_await scope::call(&lhs, fib{}, n - 1);
co_await sc.fork(&rhs, fib{}, n - 2);
co_await sc.call(&lhs, fib{}, n - 1);

co_await lf::join();
co_await sc.join();

co_return lhs + rhs;
}
Expand All @@ -41,6 +41,7 @@ void run(benchmark::State &state) {

state.counters["n"] = static_cast<double>(n);
state.counters["p"] = static_cast<double>(thread_count<Sch>(state));
state.SetComplexityN(static_cast<benchmark::IterationCount>(thread_count<Sch>(state)));

Sch scheduler = make_scheduler<Sch>(state);

Expand Down Expand Up @@ -89,9 +90,12 @@ BENCH_ALL(inline_scheduler<poly_context<geometric_stack<>, adapt_deque>>)
BENCHMARK_TEMPLATE(run, __VA_ARGS__) \
->Name(#mode "/libfork/fib/" #__VA_ARGS__) \
->Apply([](benchmark::Benchmark *b) -> void { \
for (unsigned t = 1; t <= bench_max_threads; ++t) { \
bench_thread_args(b, [](benchmark::Benchmark *b, unsigned t) { \
b->Args({fib_##mode, static_cast<std::int64_t>(t)}); \
} \
}); \
}) \
->Complexity([](benchmark::IterationCount n) -> double { \
return 1.0 / static_cast<double>(n); \
}) \
->UseRealTime();

Expand Down
18 changes: 11 additions & 7 deletions benchmark/src/libfork_benchmark/uts/libfork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ struct uts_fn {
if (num_children > 0) {
std::vector<pair> cs(static_cast<std::size_t>(num_children));

auto sc = co_await lf::scope();

for (std::size_t i = 0; i < static_cast<std::size_t>(num_children); ++i) {
cs[i].child.type = child_type;
cs[i].child.height = parent->height + 1;
Expand All @@ -37,16 +39,14 @@ struct uts_fn {
rng_spawn(parent->state.state, cs[i].child.state.state, static_cast<int>(i));
}

using scope = lf::scope<Context>;

if (i + 1 == static_cast<std::size_t>(num_children)) {
co_await scope::call(&cs[i].res, uts_fn{}, depth + 1, &cs[i].child);
co_await sc.call(&cs[i].res, uts_fn{}, depth + 1, &cs[i].child);
} else {
co_await scope::fork(&cs[i].res, uts_fn{}, depth + 1, &cs[i].child);
co_await sc.fork(&cs[i].res, uts_fn{}, depth + 1, &cs[i].child);
}
}

co_await lf::join();
co_await sc.join();

for (auto &&elem : cs) {
r.maxdepth = std::max(r.maxdepth, elem.res.maxdepth);
Expand All @@ -69,6 +69,7 @@ void run(benchmark::State &state) {
auto expected = expected_result(tree);

state.counters["p"] = static_cast<double>(thread_count<Sch>(state));
state.SetComplexityN(static_cast<benchmark::IterationCount>(thread_count<Sch>(state)));

Sch scheduler = make_scheduler<Sch>(state);

Expand All @@ -90,9 +91,12 @@ void run(benchmark::State &state) {
BENCHMARK_TEMPLATE(run, __VA_ARGS__) \
->Name(#mode "/libfork/uts/" tree_name "/" #__VA_ARGS__) \
->Apply([](benchmark::Benchmark *b) -> void { \
for (unsigned t = 1; t <= bench_max_threads; ++t) { \
bench_thread_args(b, [](benchmark::Benchmark *b, unsigned t) { \
b->Args({tree_id, static_cast<std::int64_t>(t)}); \
} \
}); \
}) \
->Complexity([](benchmark::IterationCount n) -> double { \
return 1.0 / static_cast<double>(n); \
}) \
->UseRealTime();

Expand Down
2 changes: 1 addition & 1 deletion benchmark/src/libfork_benchmark/uts/uts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

// Include the C UTS library header first (it defines max/min macros that would
// clash with std::max/std::min after import std).
#include "libfork_benchmark/uts/external/uts.h"
#include "uts/uts.h"

#undef max
#undef min
Expand Down
1 change: 1 addition & 0 deletions src/core/core.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export import :schedule;
export import :root;
export import :execute;
export import :receiver;
export import :stop;
26 changes: 7 additions & 19 deletions src/core/frame.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,15 @@ import std;

import libfork.utils;

namespace lf {
// =================== Cancellation =================== //

struct cancellation {
cancellation *parent = nullptr;
std::atomic<std::uint32_t> stop = 0;
};
import :stop;

// =================== Frame =================== //
namespace lf {

// TODO: remove this and other exports
export enum class category : std::uint8_t {
call = 0,
fork,
root,
};

export struct frame_base {};
Expand All @@ -38,7 +33,7 @@ struct frame_type : frame_base {
uninitialized<std::exception_ptr> except;

frame_type *parent;
cancellation *cancel;
stop_source::stop_token stop_token;

[[no_unique_address]]
Checkpoint stack_ckpt;
Expand All @@ -56,16 +51,9 @@ struct frame_type : frame_base {
constexpr frame_type(Checkpoint &&ckpt) noexcept : stack_ckpt(std::move(ckpt)) { joins = k_u16_max; }

[[nodiscard]]
constexpr auto is_cancelled() const noexcept -> bool {
// TODO: Should exception trigger cancellation?
for (cancellation *ptr = cancel; ptr != nullptr; ptr = ptr->parent) {
// TODO: if users can't use cancellation outside of fork-join
// then this can be relaxed
if (ptr->stop.load(std::memory_order_acquire) == 1) {
return true;
}
}
return false;
constexpr auto stop_requested() const noexcept -> bool {
// TODO: Should exception trigger stop?
return stop_token.stop_requested();
}

[[nodiscard]]
Expand Down
Loading
Loading