Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

stackless coroutine, but zero-allocation

License

NotificationsYou must be signed in to change notification settings

jamboree/coz

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Stackless coroutine for C++, but zero-allocation. Rebirth ofCO2.

Dependencies

  • Boost.Config
  • Boost.Preprocessor

Why

C++20 introduced coroutine into the language, however, in many cases (especially in async scenario), it incurs memory allocation, as HALO is not guaranteed. This creates resistance to its usage, as the convenience it offers may not worth the overhead it brings. People will tend to write monolithic coroutines instead of splitting them into small, reusable coroutines in fear of introducing too many allocations, this is contrary to the discipline of programming.

What it is

COZ is a single-header library that utilizes preprocessor & compiler magic to emulate the C++ coroutine, while requires zero allocation, it also doesn't require type erasure. With COZ, the entire coroutine is under your control, unlike standard coroutine, which can only be accessed indirectly via thecoroutine_handle.

NOTE
COZ uses stateful metaprogramming technique, which may not be blessed by the standard committee.

Overview

This library is modeled after the standard coroutine. It offers several macros to replace the language counterparts.

To use it,#include <coz/coroutine.hpp>

A coroutine written in this library looks like below:

autofunction(Args... args) COZ_BEG(promise-initializer, (captured-args...),    local-vars...;) {// for generator-like coroutineCOZ_YIELD(...);// for task-like coroutineCOZ_AWAIT(...);COZ_RETURN(...);} COZ_END

The coroutine body has to be surrounded with 2 macros:COZ_BEG andCOZ_END.

The macroCOZ_BEG takes some parameters:

  • promise-initializer - expression to initialize the promise, e.g.async<int>(exe)
  • captured-args (optional) - comma separated args to be captured, e.g.(a, b)
  • local-vars (optional) - local-variable definitions, e.g.int a = 42;

If there's nocaptured-args andlocals, it looks like:

COZ_BEG(init, ())

promise-initializer

Thepromise-initializer is an expression, whose type must define apromise_type, which will be constructed with the expression.It can take args from the function params. For example, you can take an executor to be used for the promise.

template<classExe>autof(Exe exe) COZ_BEG(async<int>(exe), ())

Remarks

  • the args (e.g.exe in above example) don't have to be in thecaptured-args.
  • if the expression contains comma that is not in parentheses, you must surround the it with parentheses (e.g.(task<T, E>)).

local-vars

You can intialize the local variables as below:

autof(int i) COZ_BEG(init, (i),    int i2 = i * 2;// can refer to the arg    std::string msg{"hello"};) ...

Remarks

  • () initializer cannot be used.
  • auto deduced variable cannot be used.

coroutine-body

Inside the coroutine body, there are some restrictions:

  • local variables with automatic storage cannot cross suspension points - you should specify them in local variables section ofCOZ_BEG as described above
  • switch body cannot contain suspension points.
  • identifiers starting with_coz_ are reserved for this library
  • Some language constructs should use their marcro replacements (see below).

After defining the coroutine body, remember to close it withCOZ_END.

Replacements for language constructs

co_await

It has 4 variants:COZ_AWAIT,COZ_AWAIT_SET,COZ_AWAIT_APPLY andCOZ_AWAIT_LET.

MACROCore Language
COZ_AWAIT(expr)co_await expr
COZ_AWAIT_SET(var, expr)var = co_await expr
COZ_AWAIT_APPLY(f, expr, args...)f(co_await expr, args...)
COZ_AWAIT_LET(var-decl, expr) {...}{var-decl = co_await expr; ...}

Remarks

  • Theexpr is either used directly or transformed.operator co_await is not used.
  • If your compiler supportsStatement Expression extension (e.g. GCC & Clang), you can useCOZ_AWAIT as an expression.However, don't use more than oneCOZ_AWAIT in a single statement, and don't use it as an argument of a function in company with other arguments.
  • f inCOZ_AWAIT_APPLY can also be a marco (e.g.COZ_RETURN)
  • COZ_AWAIT_LET allows you to declare a local variable that binds to theco_await result, then you can process it in the brace scope.

co_yield

MACROexpr Lifetime
COZ_YIELD(expr)transient
COZ_YIELD_KEEP(expr)cross suspension point

Semantic

promise.yield_value(expr);<suspend>

Remarks

  • It differs from the standard semantic, which is equivalent toco_await promise.yield_value(expr). Instead, we ignore the result ofyield_value and just suspend afterward.
  • WhileCOZ_YIELD_KEEP is more general,COZ_YIELD is more optimization-friendly.

co_return

MACROCore Language
COZ_RETURN()co_return
COZ_RETURN(expr)co_return expr

try/catch

Needed only if the try-block contains suspension points.

COZ_TRY {    ...} COZ_CATCH (const std::runtime_error& e) {    ...}catch (const std::exception& e) {    ...}

Remarks

Only the firstcatch clause needs to be written asCOZ_CATCH, the subsequent ones should use the plaincatch.

Coroutine API

coz::coroutine has interface defined as below:

template<classPromise,classParams,classState>structcoroutine {template<classInit>explicitcoroutine(Init&& init);// No copy.coroutine(const coroutine&) =delete;    coroutine&operator=(const coroutine&) =delete;    coroutine_handle<Promise>handle()noexcept;    Promise&promise()noexcept;const Promise&promise()constnoexcept;booldone()constnoexcept;voidstart(Params&& params);voidresume();voiddestroy();};

Remarks

  • Theinit constructor param is thepromise-initializer.
  • The lifetime ofPromise is tied to the coroutine.
  • Non-started coroutine is considered to bedone.
  • Don't calldestroy if it's alreadydone.

coz::coroutine_handle has the same interface as the standard one.

Customization points

coz::co_result

This defines what is returned from the coroutine.The prototype is:

template<classInit,classParams,classState>structco_result;

The first template param (i.e.Init) is the type ofpromise-initializer.Params andState are the template params that you should pass tocoz::coroutine<Promise, Params, State>, thePromise should be the same asInit::promise_type.

Users could customize it like below:

template<classParams,classState>struct [[nodiscard]] coz::co_result<MyCoroInit, Params, State> {    MyCoroInit m_init;    Params m_params;// optionalautoget_return_object();    ...};

Remarks

  • co_result will be constructed the with thepromise-initializer and thecaptured-args.
  • ifget_return_object is defined, its result is returned; otherwise, theco_result itself is returned.

Promise

The interface forPromise looks like below:

structPromise {voidfinalize();// eithervoidreturn_void();// orvoidreturn_value();voidunhandled_exception();// optionalautoawait_transform(auto expr);};

Remarks

  • There's noinitial_suspend andfinal_suspend.The user should callcoroutine::start to start the coroutine.
  • Once the coroutine stops (either normally or viadestroy) thePromise::finalize will be called.
  • await_transform is not greedy (i.e. could be filtered by SFINAE).

Awaiter

The interface forAwaiter looks like below:

structAwaiter {boolawait_ready();// eithervoidawait_suspend(coroutine_handle<Promise> coro);// orboolawait_suspend(coroutine_handle<Promise> coro);    Tawait_resume();};

Remarks

  • Unlike standard coroutine,await_suspend cannot returncoroutine_handle.

License

Copyright (c) 2024 JamboreeDistributed under the Boost Software License, Version 1.0. (See accompanyingfile LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

About

stackless coroutine, but zero-allocation

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp