Skip to content

Instantly share code, notes, and snippets.

@kirkshoop
Last active June 4, 2024 13:10
Show Gist options
  • Save kirkshoop/f6ab49ec54a323e6033789ceb9f46752 to your computer and use it in GitHub Desktop.
Save kirkshoop/f6ab49ec54a323e6033789ceb9f46752 to your computer and use it in GitHub Desktop.
C++ ABI with versioning and backwards compatibility
/*
* Copyright (c) 2024 Lee Howes, Lucian Radu Teodorescu
*
* Licensed under the Apache License Version 2.0 with LLVM Exceptions
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://llvm.org/LICENSE.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __EXEC__SYSTEM_CONTEXT_IF_H__
#define __EXEC__SYSTEM_CONTEXT_IF_H__
#include <stdint.h>
namespace std::abi {
// https://datatracker.ietf.org/doc/html/rfc4122
struct generate_t {};
struct uuid_t {
// initialize nil uuid 00000000-0000-0000-0000-000000000000
constexpr uuid_t();
// generate new uuid
uuid_t(generate_t);
// initialize from string at compile-time
// example uuid_t{"00000000-0000-0000-0000-000000000000"sv}
consteval uuid_t(std::string_view uuid);
constexpr uuid_t(
uint32_t time_low,
uint16_t time_mid,
uint16_t time_hi_and_version,
uint8_t clock_seq_hi_and_reserved,
uint8_t clock_seq_low,
std::span<std::byte, 6> node);
constexpr uuid_t(
uint32_t time_low,
uint16_t time_mid,
uint16_t time_hi_and_version,
std::span<std::byte, 8> node);
// https://datatracker.ietf.org/doc/html/rfc4122#section-4.3
// create a version 5 (SHA-1) UUID using a "name" from a "name space"
// SHA-1 http://www.itl.nist.gov/fipspubs/fip180-1.htm
consteval uuid_t(uuid_t ns, std::string_view name);
constexpr uuid_t(const uuid_t&);
uint32_t time_low;
uint16_t time_mid;
uint16_t time_hi_and_version;
uint8_t clock_seq_hi_and_reserved;
uint8_t clock_seq_low;
std::byte node[6];
// uuid_compare -- Compare two UUID's "lexically" and return
// -1 u1 is lexically before u2
// 0 u1 is equal to u2
// 1 u1 is lexically after u2
// Note that lexical ordering is not temporal ordering!
friend auto operator<=>(const uuid_t&, const uuid_t&);
};
// Name string is an ISO C++ ABI interface id
inline static constexpr uuid_t NameSpace_CPPAID{ // 31e5da35-7745-46f4-a241-7cac52889433
0x31e5da35,
0x7745,
0x46f4,
0xa2,
0x41,
{0x7c, 0xac, 0x52, 0x88, 0x94, 0x33}
};
// Name string is an ISO C++ ABI object id
inline static constexpr uuid_t NameSpace_CPPAOD{ // 584fc8d0-15ac-4c43-a5be-07b4e9f68dc3
0x584fc8d0,
0x15ac,
0x4c43,
0xa5,
0xbe,
{0x07, 0xb4, 0xe9, 0xf6, 0x8d, 0xc3}
};
/// all interfaces derive from this to allow selecting other interfaces
struct __abi_base_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::abi::__abi_base_interface@1.0"sv};
/// select a different interface - might fail
/// result is then static_cast<derived-interface>(select-result)
/// to the derived-interface type that corresponds to the given interfaceId
virtual __abi_base_interface* __interface_select(uuid_t interfaceId) = 0;
/// returns the object id for the derived-object that is providing this interface
virtual uuid_t __get_derived_object_id() = 0;
/// returns the interface id for the derived-interface type
/// static_cast<derived-interface>(this)
virtual uuid_t __get_derived_interface_id() = 0;
/// only allow pointers to __abi_base_interface.
/// there are no instances of __abi_base_interface
__abi_base_interface(__abi_base_interface&&) = delete;
__abi_base_interface(const __abi_base_interface&) = delete;
__abi_base_interface& operator=(__abi_base_interface&&) = delete;
__abi_base_interface& operator=(const __abi_base_interface&) = delete;
private:
virtual ~__abi_base_interface();
__abi_base_interface() {}
};
/// Interface that reports that a failure occurred
struct __abi_failure_interface : __abi_base_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::abi::__abi_failure_interface@1.0"sv};
/// get text message description
std::string_view __get_description() = 0;
};
/// Interface that reports that an object id is not implemented
struct __abi_object_not_implemented : __abi_failure_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__abi_object_not_implemented@1.0"sv};
/// get object id that is not implemented
uuid_t __get_object_id() = 0;
};
/// Interface that reports that an interface id is not implemented
struct __abi_interface_not_implemented : __abi_failure_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__abi_interface_not_implemented@1.0"sv};
/// get interface id that is not implemented
uuid_t __get_interface_id() = 0;
};
/// unique object handle
struct __abi_unique_object {
inline static constexpr uuid_t objectId{NameSpace_CPPAOD, "std::abi::__abi_unique_object@1.0"sv};
/// ensure that any deallocation occurs
/// in the same TU that allocated the object.
virtual ~__abi_unique_object() = 0;
__abi_base_interface* __get_base_interface() = 0;
__abi_unique_object();
__abi_unique_object(const __abi_unique_object&) = delete;
private:
__abi_base_interface* root;
};
struct __abi_storage_info {
inline static constexpr uuid_t objectId{NameSpace_CPPAOD, "std::abi::__abi_storage_info@1.0"sv};
uint32_t __size;
uint32_t __alignment;
};
// ask for a description of the storage needed for an object
__abi_storage_info __abi_get_std_object_storage_info(
// uuid of object to construct
uuid_t objectId,
// interface that provides access to any arguments used by the object constructor
// (might affect the size of the storage needed)
__abi_base_interface* argument);
// make an object and return a handle to the object.
// The object handle will support __abi_failure_interface
// when this function fails to construct the object
__abi_unique_object __abi_make_std_object(
// space in which to store object
std::span<std::byte, std::dynamic_extent> storage,
// uuid of object to construct
uuid_t objectId,
// interface that provides access to any anarguments used by the object constructor
__abi_base_interface* argument);
} // namespace std::abi
namespace std::execution {
struct __scheduler_interface;
/// Interface that allows interaction with the system context, allowing scheduling work on the system.
struct __context_interface : __abi_base_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__context_interface@1.0"sv};
/// Returns an interface to the system scheduler.
__scheduler_interface* __get_scheduler() = 0;
};
/// The object handle will support __abi_failure_interface
/// when this function fails to construct the object
__abi_unique_object __get_system_context() = 0;
/// The object handle will support __abi_failure_interface
/// when this function fails to construct the object
__abi_unique_object __get_main_context() = 0;
/// Interface to query values that describe the behaviour of an object implementation
struct __queries_interface : __abi_base_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__queries_interface@1.0"sv};
/// access values that describe the behaviour of this object
__queries_interface* __query(uuid_t interfaceId) = 0;
};
/// Interface to ask for a memory_resource
struct __get_memory_resource_interface : __queries_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__get_memory_resource_interface@1.0"sv};
/// access a memory_resource
std::pmr::memory_resource* __get_memory_resource(uuid_t interfaceId) = 0;
};
/// Interface for a universal receiver
struct __environment_provider_interface : __abi_base_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__environment_provider_interface@1.0"sv};
__queries_interface* __get_env() = 0;
};
/// Interface for a universal receiver
struct __receiver_interface : __environment_provider_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__receiver_interface@1.0"sv};
void __set_value(__abi_base_interface* value) = 0;
void __set_error(__abi_failure_interface* error) = 0;
void __set_stopped() = 0;
};
/// Interface for a universal operation-state
struct __operation_state_interface : __abi_base_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__operation_state_interface@1.0"sv};
void __start() = 0;
};
/// Interface for a universal sender
struct __sender_interface : __abi_base_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__sender_interface@1.0"sv};
/// The info for storage of the operation state object on the implementation side.
__abi_storage_info __get_operation_state_storage_info(__receiver_interface*) = 0;
/// make an operation-state
/// The object handle will support __abi_failure_interface
/// when this function fails to construct the object
__abi_unique_object __connect(
std::span<std::byte, std::dynamic_extent> storage,
__receiver_interface* receiver) = 0;
};
/// Interface that allows interaction with the system context, allowing driving work from the scheduler.
struct __drivable_interface : __abi_base_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__drivable_interface@1.0"sv};
/// The info for storage of the when_ready sender object on the implementation side.
__abi_storage_info __get_when_ready_sender_storage_info() = 0;
/// make an operation-state
/// The object handle will support __abi_failure_interface
/// when this function fails to construct the object
__abi_unique_object __when_changed() = 0;
/// The info for storage of the when_ready sender object on the implementation side.
__abi_storage_info __get_dispatch_some_sender_storage_info() = 0;
/// make an operation-state
/// The object handle will support __abi_failure_interface
/// when this function fails to construct the object
__abi_unique_object __dispatch_some() = 0;
};
enum __forward_progress_guarantees {
concurrent = 0,
parallel = 1,
weakly_parallel = 2
};
/// Interface to report forward-progress-guarantee
struct __forward_progress_guarantee_interface : __queries_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__forward_progress_guarantee_interface@1.0"sv};
/// The forward progress guarantee of the scheduler.
///
/// 0 == concurrent, 1 == parallel, 2 == weakly_parallel
__forward_progress_guarantees __get_forward_progress_guarantee() = 0;
};
struct __scheduler_interface : __queries_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__scheduler_interface@1.0"sv};
/// The info for storage of the operation state object on the implementation side.
__abi_storage_info __get_schedule_sender_storage_info() = 0;
/// make a new sender that will add new work on the scheduler
/// The object handle will support __abi_failure_interface
/// when this function fails to construct the object
__abi_unique_object __schedule(std::span<std::byte, std::dynamic_extent> storage) = 0;
};
/// Interface to query values that describe the behaviour of an object implementation
struct __bulk_item_interface : __abi_base_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__bulk_item_interface@1.0"sv};
/// process data referred to by index
void __process_item(uint64_t index, __abi_base_interface* data) = 0;
};
struct __bulk_scheduler_interface : __queries_interface {
inline static constexpr uuid_t interfaceId{NameSpace_CPPAID, "std::execution::__bulk_scheduler_interface@1.0"sv};
/// The info for storage of the operation state object on the implementation side.
__abi_storage_info __get_bulk_sender_storage_info() = 0;
/// Returns a sender that schedules new bulk work of size `shape` on the scheduler, calling `itemFn` with `data`
/// for indices in [0, `shape`).
/// The object handle will support __abi_failure_interface
/// when this function fails to construct the object
__abi_unique_object __bulk_schedule(
std::span<std::byte, std::dynamic_extent> storage,
uint64_t shape,
__abi_base_interface* data,
__bulk_item_interface* itemFn) = 0;
};
} // namespace std::execution
#endif
namespace cthash::utility {
template <std::unsigned_integral T, std::size_t N = sizeof(T)> constexpr T little_endian_bytes_to(std::span<const std::byte, N> input) requires(N != std::dynamic_extent) {
static_assert(N <= sizeof(T));
return [=]<std::size_t... Idx>(std::index_sequence<Idx...>) {
return static_cast<T>(((static_cast<T>(input[Idx]) << (Idx * 8u)) | ...));
}(std::make_index_sequence<N>());
}
template <std::unsigned_integral T, std::size_t N = sizeof(T)> constexpr T big_endian_bytes_to(std::span<const std::byte, N> input) requires(N != std::dynamic_extent) {
constexpr std::size_t base = N * 8u - 8u;
static_assert(N <= sizeof(T));
return [=]<std::size_t... Idx>(std::index_sequence<Idx...>) {
return static_cast<T>(((static_cast<T>(input[Idx]) << (base - (Idx * 8u))) | ...));
}(std::make_index_sequence<N>());
}
} // namespace cthash::utility
namespace cthash {
struct uuid: std::array<std::byte, 16> {
using super = std::array<std::byte, 16>;
using super::super;
struct position_and_size {
size_t position;
size_t size;
};
static constexpr auto TIME_HIGH = position_and_size{0, 4};
static constexpr auto TIME_LOW = position_and_size{4, 2};
static constexpr auto RESERVED = position_and_size{6, 2};
static constexpr auto FAMILY = position_and_size{8, 1};
static constexpr auto FAMILY_AND_FIRST_BYTE_OF_NODE = position_and_size{8, 2};
static constexpr auto NODE = position_and_size{9, 7};
static constexpr auto REMAINING_OF_NODE = position_and_size{10, 6};
static constexpr size_t time_low_pos = 4u;
static constexpr size_t time_low_size = 2u;
template <position_and_size Info> constexpr auto member() const noexcept {
return std::span(*this).template subspan<Info.position, Info.size>();
}
template <typename CharT, typename Traits> friend auto operator<<(std::basic_ostream<CharT, Traits> & os, const uuid & value) -> std::basic_ostream<CharT, Traits> & {
constexpr auto hexdec = "0123456789abcdef";
const auto print = [&os]<std::size_t N>(std::span<const std::byte, N> in) {
for (std::byte v: in) {
os << hexdec[unsigned(v) >> 4u] << hexdec[unsigned(v) & 0xFu];
}
};
// time_high
print(value.member<TIME_HIGH>());
os << '-';
// time_low
print(value.member<TIME_LOW>());
os << '-';
// reserved
print(value.member<RESERVED>());
os << '-';
// family
print(value.member<FAMILY_AND_FIRST_BYTE_OF_NODE>());
os << '-';
// node
print(value.member<REMAINING_OF_NODE>());
return os;
}
};
template <typename T> concept uuid_like = requires(const T & in) {
{ in.time_high() } -> std::same_as<uint32_t>;
{ in.time_low() } -> std::same_as<uint16_t>;
{ in.reserved() } -> std::same_as<uint16_t>;
{ in.family() } -> std::same_as<uint8_t>;
{ in.node() } -> std::same_as<uint64_t>;
requires(T().size() == 16);
requires std::same_as<typename T::value_type, std::byte>;
};
struct uuid_v1: uuid {
using super = uuid;
using super::super;
constexpr auto time_high() const noexcept {
return utility::big_endian_bytes_to<uint32_t>(super::member<TIME_HIGH>());
}
constexpr auto time_low() const noexcept {
return utility::big_endian_bytes_to<uint16_t>(super::member<TIME_LOW>());
}
constexpr auto reserved() const noexcept {
return utility::big_endian_bytes_to<uint16_t>(super::member<RESERVED>());
}
constexpr auto family() const noexcept {
return utility::big_endian_bytes_to<uint8_t>(super::member<FAMILY>());
}
constexpr auto node() const noexcept {
return utility::big_endian_bytes_to<uint64_t>(super::member<NODE>());
}
};
struct uuid_v2: uuid {
using super = uuid;
using super::super;
constexpr auto time_high() const noexcept {
return utility::little_endian_bytes_to<uint32_t>(super::member<TIME_HIGH>());
}
constexpr auto time_low() const noexcept {
return utility::little_endian_bytes_to<uint16_t>(super::member<TIME_LOW>());
}
constexpr auto reserved() const noexcept {
return utility::little_endian_bytes_to<uint16_t>(super::member<RESERVED>());
}
constexpr auto family() const noexcept {
return utility::big_endian_bytes_to<uint8_t>(super::member<FAMILY>());
}
constexpr auto node() const noexcept {
return utility::big_endian_bytes_to<uint64_t>(super::member<NODE>());
}
};
template <uuid_like T> consteval auto parse(std::string_view input) -> T {
auto reader_it = input.begin();
const auto lookahead = [&reader_it, input] -> char {
if (reader_it == input.end()) {
throw "unexpected end";
}
return *reader_it;
};
const auto skip = [&] {
++reader_it;
};
const auto read_one = [&] -> char {
const char c = lookahead();
skip();
return c;
};
const auto read_one_if = [&](char expected) {
if (lookahead() == expected) {
skip();
return true;
} else {
return false;
}
};
const auto read_and_ensure = [&](char expected) {
if (lookahead() != expected) {
throw "unexpected character";
}
skip();
};
const auto from_hexdec = [](char c) -> int {
if (c >= '0' && c <= '9') {
return c - '0';
} else if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
} else if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
} else {
throw "unexpected character in input";
}
};
const auto read_octet = [&] -> std::byte {
const auto hi = from_hexdec(read_one());
const auto lo = from_hexdec(read_one());
return static_cast<std::byte>(hi << 4u | lo);
};
auto output = T{};
// parsing...
const bool with_braces = read_one_if('{');
// time_high
output[0] = read_octet();
output[1] = read_octet();
output[2] = read_octet();
output[3] = read_octet();
// time_low
const bool with_dashes = read_one_if('-');
output[4] = read_octet();
output[5] = read_octet();
// reserved
if (with_dashes) { read_and_ensure('-'); }
output[6] = read_octet();
output[7] = read_octet();
// family
if (with_dashes) { read_and_ensure('-'); }
output[8] = read_octet();
// node
output[9] = read_octet();
if (with_dashes) { read_and_ensure('-'); }
output[10] = read_octet();
output[11] = read_octet();
output[12] = read_octet();
output[13] = read_octet();
output[14] = read_octet();
output[15] = read_octet();
if (with_braces) { read_and_ensure('}'); };
if (reader_it != input.end()) {
throw "additional data at end";
}
return output;
}
namespace literals {
consteval auto operator"" _uuid_v1(const char * data, size_t length) {
return cthash::parse<uuid_v1>(std::string_view(data, length));
}
consteval auto operator"" _uuid_v2(const char * data, size_t length) {
return cthash::parse<uuid_v2>(std::string_view(data, length));
}
} // namespace literals
} // namespace cthash

ABI Design Notes

Author: Kirk Shoop

feature description
link-time
replaceable
Allows the std library to know at link-time which services to construct.
Allows specified construction order of services by the std library.
Prevents any need to address the complexities of runtime replacement in the specification or in the implementation (eg. when to construct, when to destruct, how many replacements allowed, etc..)
cannonical
across all stdlib
implementations
libraries like TBB and Qt implement a replacement once and that works with all std libraries.
user provided
storage
minimize allocations by allowing a user to supply storage for an operation
user-provided allocator Allows the user to determine how operations allocate additional storage
specified
construction
order
The order of the construction of services provided by the std lib is specified in start and term sections of the std
compile-time
versioned
Specify how to version types that change or extend the ABI.
When additional features are added to the system context how does that look in the type-system?
std::execution::system_context -> std::execution::system_context2?
or
std::execution::v1::system_context -> std::execution::v2::system_context?
run-time
versioned
The user can inspect the run-time version of the implementation and use that to select a different code-path.
This allows an implementation of v2 system_context to be provided by the std library as a v1 system_context and then when the v1 system_context is passed by an app or a library to a library that is aware of v2, the library will be able to recognize at runtime that the provided v1 system_context has a v2 implementation and use the v1 system_context as a v2 system_context
run-time
extensible
A replacement can expose additional functionality and code has a mechanism to query for that functionality at runtime
An example might be that a replacements provided by hpux, TBB, Qt, and ASIO might expose tuning options and additional features like IO
backward-
compatible
This means that it is specified how existing code using the system_context and existing replacements of the system_context are not affected by changes to the ABI over time.
The ABI explicitly specifies how to: add features, remove features, and change features, without breaking any existing users or libraries or std libraries or compiled binaries.
feature Paper POC godbolt
link-time replaceable
cannonical across all
stdlib implementations
user provided storage
user-provided allocator
specified construction order
compile-time versioned
run-time versioned
run-time extensible
backward-compatible

Goals

  • An ABI should be defined in a way that avoids or prevents common programming mistakes
    • mistakes like: buffer overruns, deadlocks, races, resource leaks, bad casts, units represented by storage types like ‘int’, unversioned ABIs
    • examples:
      • Use struct with ptr and count instead of separate pointer and count arguments
        • Add functions to manipulate these safely
        • Write adapters to idiomatic forms in each language (std::span etc..)
      • Use struct for each unit that contains a value member instead of native arithmetic types
        • Add functions to manipulate these safely
        • Write adapters to idiomatic forms in each language (std::chrono::nanoseconds etc..)
      • Define a canonical unit as the abstraction unit and then do conversions to and from the canonical unit. A balance must be achieved in the abstraction that favors safety.
        • If nanometers, then no inches, etc..
        • If kelvin, then no fahrenheit or celsius, etc..
        • Rationale
          • reduces the size of the abstraction API
          • reduces bugs.
          • increases safety
        • Effects
          • increases errors in the values as they are converted.
          • increases overhead as they are converted.
  • An ABI should provide structured-concurrency
    • all resources are tied to (async-)function-scopes
    • exclude
      • create_handle() -> handle & close_handle(handle) -> void
  • An ABI should be replaceable
    • link-time replacement of a function in order to replace a whole component with a different implementation of the ABI
  • An ABI should be ‘canonical’
    • same across all std library implementations
  • An ABI should be versioned
    • features added in new versions
    • features deprecated in old versions
    • features replaced by new versions
  • An ABI should be backward-compatible (no limits on changes to the std library that modify the ABI)
    • old binaries are unaffected by
      • features added
      • features deprecated
      • features replaced
  • An API, expressed in terms of an ABI, will likely need to be supported for 10+ years

Sketch

Sketch of such an ABI for the C++ system-executor

component object model

Microsoft defined COM in the 80s

COM used a tool called 'midl' to generate C and C++ definitions of interfaces that were structs with vtables in C and abstract bases in C++.

Each interface has an associated uuid that uniquely identifies that interface. interface + uuid are immutable. This creates ABI-stability and backwards-compatibility.

Change occurrs by:

  • adding an interface + uuid that extends an existing interface + uuid
    • creates a new vtable that includes all of the old vtable functions and appends new funtions to the end of the vtable
  • adding a new interface + uuid that replaces an existing interface + uuid
    • new vtable does not include the functions from the vtable of an existing interface + uuid
  • add an interface + uuid to the implementation of an object
  • remove an interface + uuid from the implementation of an object

The COM model of using an abstract base interface paired with a unique uuid value has a proven track record of allowing:

  • extensive modifications of object implmentations with no impact on existing binaries using those implementations
  • major features added with no impact on existing binaries that are using other features

IUnknown

Every COM interface includes IUnknown.

IUnknown has two services.

  1. ref-count managed lifetime
  2. interface selection by uuid

interface selection by uuid

All COM interfaces allow changing to a different interface by calling QueryInterface with a uuid for the interface desired. The query will fail when the underlying object does not implement the given interface + uuid.

Replicate? Yes

uuid generation

uuid assigned by generating new uuids from scratch for each interface

Replicate? No

selected interface returned as a void*

COM QueryInterface has a void** argument in which the selected interface pointer is placed.

Replicate? No

object identity

All COM interfaces include IUnknown.

The way to check two different interfaces for Object Identity depends on queryies for IUnknown being cannonical across all interfaces on the same object.

If two interfaces are queried for exactly IUnknown, then the pointers returned from both queries must have the same value when the given interfaces were retrieved from the same underlying object.

Replicate? Yes

ref-counting

All COM interfaces share Ownership of the underlying object. There are no Ref semantics. There are no Unique semantics.

Replicate? No

HRESULT

COM created an integer value type that was expected to hold a superset of all errors across all time, across all features.

Replicate? No

Rationale:

  • callers must handle every possible error at every call site
    • usually this is reduced to a binary if ()

BSTR

COM created its own string type. A BSTR was a pointer to a string that was preceded by a UINT16 count of capacity (allowing embedded null chars in a string). It also required that BSTR were allocated from a dedicated BSTR allocator.

Replicate? No

Rationale:

  • negative indexing to retrieve untyped struct members and then reinterpret_cast to a type is not safe
  • forcing allocation limits the usage in no-alloc targets
  • designating a single allocator implementation does not allow using fit-for-purpose allocators

VARIANT

VARIANT is a tag union of a fixed set of POD types. This has been a severe limitation when designing interfaces that will pass values whose type is not known at the time that the interface + uuid was defined.

Replicate? No

ABI design

namespace UUIDs

The uuid v5 namespace ids for abi interfaces and objects:

namespace std::abi::v1 {
  struct interface_id_t {
    inline static constexpr uuid_t namespace_id = /* 31e5da35-7745-46f4-a241-7cac52889433 */;
    uuid_t value;
  };
  struct object_id_t {
    inline static constexpr uuid_t namespace_id = /* 584fc8d0-15ac-4c43-a5be-07b4e9f68dc3 */;
    uuid_t value;
  };
} // namespace std::abi::v1

base interface

The abstract base struct, std::abi::v1::base::interface, that all interfaces derive from.

namespace std::abi::v1::base {

enum class select_interface_expected_t : std::uint8_t {
    select_interface_expected_uninitialized = 0,
    not_implemented = 1,
    implemented = 2
};

struct interface_t;

struct select_interface_result_t {
  select_interface_expected_t expected;
  object_id_t object_id;
  interface_id_t interface_id;
  interface_t* value;
};

struct interface_t {
  inline static constexpr interface_id_t id{/* "base::interface_t" */};

  virtual ~interface_t() {}

  virtual auto select_interface(interface_id_t) noexcept -> select_interface_result_t = 0;
};

} // namespace std::abi::v1::base

base interface ref type

The ref wrapper to simplify correct usage of base::interface_t

namespace std::abi::v1::base {

template<std::derived_from<interface_t> Interface>
struct ref_t {
  using interface_t = Interface;
  inline static constexpr interface_id_t id{interface_t::id};

  ~ref_t() noexcept { value == nullptr; }

  ref_t() = delete;

  explicit ref_t(object_id_t oid, Interface* i) noexcept :
    object_id(oid),
    value(i) { 
    if (!i) { std::terminate(); }
  }

  ref_t(const ref_t&) = default;
  ref_t& operator=(const ref_t&) = default;

  ref_t(ref_t&& o) noexcept : 
    object_id(o.object_id), 
    value(std::exchange(o.value, nullptr)) {
  }
  ref_t& operator=(ref_t&& o) noexcept {
    object_id = o.object_id;
    value = std::exchange(o.value, nullptr);
  }

  template<std::derived_from<interface_t> DesiredInterface>
  auto select_interface() const noexcept
    -> std:expected<
      ref_t<DesiredInterface>, 
      std::tuple<interface_id_t, object_id_t>> {
    select_interface_result_t result = value->select_interface(DesiredInterface::id);
    if (select_interface_expected_t::implemented != result.expected) {
      return std::make_tuple(DesiredInterface::id, result.object_id);
    }
    return ref_t<DesiredInterface>{result.object_id, static_cast<DesiredInterface*>(result.value)};
  }

  Interface* get_native() const noexcept { 
    return value; 
  }

  object_id_t get_object_id() const noexcept { 
    return object_id; 
  }

private:
  object_id_t object_id;
  interface_t* value;
};

} // namespace std::abi::v1::base

object representation

The implementation of a destructible object

namespace std::abi::v1::object {

struct interface_t : base::interface_t {
  inline static constexpr interface_id_t id{/* "object::interface_t" */};

  virtual auto destruct() noexcept -> void = 0;
};

template<class Object>
struct ref_t : base::ref_t<Object::interface_t> {
  ~ref_t() {
    Object::object_t* o = std::exchange(object, nullptr);
    o->destruct();
  }

  explicit ref_t(Object::object_t* o, Object::interface_t* i) noexcept :
    base::ref_t<Object::interface_t>(Object::id, i),
    object(o) { 
    if (!i) { std::terminate(); }
  }

private:
  Object::object_t* object;
};

} // namespace std::abi::v1::object

facory representation

The implementation of an object factory

namespace std::abi::v1::factory {

enum class construct_expected_t : std::uint8_t {
    construct_expected_uninitialized = 0,
    not_implemented = 1,
    implemented = 2
};

struct construct_result_t {
  construct_expected_t expected;
  object_id_t object_id;
  interface_id_t interface_id;
  base::interface_t* base;
  object::interface_t* value;
};

struct storage_info_t {
  construct_expected_t expected;
  std::uint32_t size;
  std::uint16_t alignment;
};

struct storage_t {
  std::byte* begin;
  std::byte* end;
};

struct interface_t : base::interface_t {
  inline static constexpr interface_id_t id{/* "factory::interface_t" */};

  virtual auto get_storage_info(object_id_t, base::interface_t*) noexcept -> storage_info_t = 0;
  virtual auto construct(object_id_t, storage_t, base::interface_t*) noexcept -> construct_result_t = 0;
};

template<std::derived_from<interface_t> Factory>
struct ref_t : base::ref_t<Factory> {

  template<class Object>
  auto get_storage_info(base::ref_t<Object::arguments_t> args) const noexcept 
    -> std:expected<
      std::tuple<std::uint32_t, std::uint16_t>, 
      object_id_t> {
    storage_info_t info = get_native()->get_storage_info(Object::id, args.get_native());
    if (construct_expected_t::implemented != info.expected) {
      return Object::id;
    }
    return std::make_tuple(info.size, info.alignment);
  }

  template<class Object>
  auto construct(std::span<std::byte> storage, base::ref_t<Object::arguments_t> args) const noexcept 
    -> std:expected<
      object::ref_t<Object>, 
      std::tuple<interface_id_t, object_id_t>> {
    construct_result_t result = get_native()->construct(
      Object::id, storage_t{storage.begin(), storage.end()}, args.get_native());
    if (construct_expected_t::implemented != result.expected) {
      return std::make_tuple(Object::interface_t::id, result.object_id);
    }
    return object::ref_t<Object>{
      static_cast<Object::object_t*>(result.value), 
      static_cast<Object::interface_t*>(result.base)};
  }
};

} // namespace std::abi::v1::factory

empty object

The implementation of an empty object

namespace std::abi::v1 {

struct empty_object_t : base::interface_t {
  inline static constexpr object_id_t id{/* "empty_object_t" */};
  using object_t = object::interface_t;
  using arguments_t = base::interface_t;
  using interface_t = base::interface_t;

  override auto select_interface(interface_id_t desired) noexcept -> select_interface_result_t {
    if (interface_t::id == desired) {
      return {{id, interface_t::id, static_cast<interface_t*>(this)}, select_interface_expected_t::implemented};
    }
    return {{id, interface_t::id, nullptr}, select_interface_expected_t::not_implemented};
  }
};

inline static constexpr empty_object_t empty_object{};
inline static constexpr base::ref_t<base::interface_t> ref_empty_object{empty_object_t::id, &empty_object};

} // namespace std::abi::v1

uuidv5

uuid is an IETF RFC https://datatracker.ietf.org/doc/html/rfc4122

There are multiple versions of uuid defined. v5 (rfc4122#section-4.3) has namespacing properties that fit an ABI very well.

A version 5 (SHA-1) UUID is created using SHA-1 to hash a "name" string and a "namespace" uuid

SHA-1: http://www.itl.nist.gov/fipspubs/fip180-1.htm

A namespace uuid is a normal uuid generated in the normal way uuid_create()

A v5 uuid can be created by starting with a namespace uuid and using SHA-1 to hash the namespace uuid and a given string "name" together and use the output of the SHA-1 hash to produce the v5 uuid.

Hana Dusikova started writing a constexpr uuid_t: https://github.com/hanickadot/cthash/blob/feature/uuid/examples/uuid.cpp

For an ABI, an abstract base

struct mylib::my_interface : std::abi::v1::base::interface_t {..};

would have a v5 uuid built by

uuid_v5_create(std::abi::v1::interface_id_t::namespace_id, "mylib::my_interface");

each ABI version would generate a new interface namespace uuid and a new object namespace uuid, and use those to create all the v5 uuids for the interfaces and objects in that version of the ABI.

void*

void* casts are not needed. static_cast is safer than reinterpret_cast, so having an abstract base interface struct that all other interfaces inherit from, allows reinterpret_cast to be elimiinated.

Have an abstract base struct ala std::abi::v1::base::interface_t

the base interface contains a select_interface function that takes an std::abi::v1::interface_id.

the select_interface function returns std::abi::v1::base::select_interface_result_t

the select_interface_result_t struct has three members: expected, object_id, and value

  • expected reports the expected conditions: not_implemented, or implemented
  • object_id reports the uuid of the object implementing the given interface_id
  • value contains a pointer that can be static_cast to the interface associated with the selected interface_id.

vtable selection

This is an ABI form of dynamic_cast that requires no RTTI from the compiler. All the type information (interface + uuid) is defined by the ABI.

  • allow an object to implement N different interface + uuid and let the user select which interface + uuid they want to use.
    • allow new interfaces to replace old ones.
    • allow multiple representations for data. an object that supports an interface that provides a string and an interface that provides functions for an URL contained in the string.
    • support a value type that has infinite variants. an object might support an interface that provides a string or instead support an interface that provides a float or instead support an interface that provides an URL.

variant

vtable selection provides an infinite variant with no fixed size.

an object might support an interface that provides a string or instead support an interface that provides a float or instead support an interface that provides an URL, to infinity..

object lifecycle

An object will have a point of creation into some storage and a point of destruction of the object in some storage.

ref-counting an object will be an opt-in feature of the user of an object and this will not affect the ABI of the object.

object storage

A solution for storage involves some difficult tradeoffs.

There is a need to support:

  • zero-alloc systems where all storage is statically known at compile-time.
  • dynamic replacement of the ABI implementation - without recompiling the binary using the ABI.
  • different implementations with different object sizes and alignments.

Conflicts:

  • If there is a fixed size defined for each declared object, then all ABI implementations will be constrained to that size.
  • If there is a dynamic query for the storage info required for each declared object, then all users of the ABI must either over-size static reserved storage based on a guess or must dynamically allocate.

It may be possible to separate the cases enough to reduce the impact of the conflicts.

  • Decide that the declaration of an object includes a static-size that implementations, that support dynamic replacement, must be implemented to fit in the declared static-size. The implementations are allowed to use internal allocations in this case.
  • Decide that zero-alloc systems must recompile in order to change the implementation of the ABI. Dynamic ABI replacement is not allowed for zero-alloc systems.
  • Decide that implementations that support zero-alloc systems are allowed to define replacement objects whose declarations include an implementation-specific size
    • creates a new ABI that is implementation-specific and thus is not replaceable with another implementation
    • allows updates of the implementation to retain other features provided by an ABI
      • eg. maintain backwards compatibility, versioning, ..

Other:

The user of an object should be in control of where the object is stored. This is a requirement to zero-alloc that might need to be relaxed for dynamic replacement of the implementation.

Different implementations of the object are allowed to have different storage requirements.

In the case of an object exposed in an ABI, the object declaration will need to provide the information for the size and alignment. All implementations that support dynamic replacement, are required to support the declared storage for their object implemetation.

The constructor for an object exposed in an ABI will take a reference to the storage for the object.

The constructor will require that the storage provided is sufficient for the object. The program will terminate when there is not enough storage provided. This would happen if an implementation that did not support dynamic replacement was used with a binary that was compiled to support dynamic replacement

Async Allocation:

It is possible for an implementation that requires larger storage to keep an internal pool of storage and allocate that to supplement the user supplied storage.

Lewis implemented an io-scheduler for Microsoft IOCP that had a fixed pool of internal state that contained IOCP records. Scheduling a new IO operation would have a fixed size that was used to add the request to an intrusive list of pending IO until an IOCP slot became available after which it was moved to an intrusive list of in-flight IO.

This approach effectivly hides an async allocation on a fixed-size pool of object storage behind an async function. This works for objects when the object construction is an async-function.

code-bugs

In many systems precondition failures and code-bugs are represented as errors.

Examples: invalid-argument, invalid-pointer, out-of-range,

There is no valid representation of a code-bug or precondition failure. These failures must result in process termination.

This is required for ABI stability for callers across ABI implementations. The alternative is that implementors have to support every creative invalid usage of an ABI in every version/implementation that provides that ABI 'bug-compatible'.

This includes each and every std library implementation needing to maintain 'bug-compatible' implementations even when that makes new features less efficient to implement or more complicated to support.

The ABI must define all preconditions for each function so that the caller of a function can guarantee the the function will not encounter a pre-condition failure.

results

A function result has expected states and output values.

expected states are specific to each function.

changing the set of expected result states for a function changes the ABI, just like changing the arguments for a function changes the ABI.

expected states provide information about the state when the function exited.

expected states can indicate:

  • the state of one or more disjoint post-conditions as enum or flag-set
    • corrupt-data (parsing untrusted data)
    • not-found (a try-.. method did not produce a value)
    • disconnected
  • additional state
    • end-of-file
    • default-value

expected states must be handled by every caller. The design of the function ABI must balance the usage complexity of many expected states with the impact of leaving states that cannot be represented as an expected state, which will then result in program termination when those states are encountered.

Examples of ABI changes over time

myfoo interface - original

namespace mylib::myfoo {

using namespace std::abi::v1;

struct interface_t : base::interface_t {
  inline static constexpr interface_id_t id{/* "mylib::myfoo::interface_t" */};

  virtual auto hello() noexcept -> void = 0;
};

template<std::derived_from<interface_t> Foo>
struct ref_t : base::ref_t<Foo> {
  void hello() noexcept {
    get_native()->hello();
  }
};

} // namespace mylib::myfoo 

myfooworld interface - extends myfoo

namespace mylib::myfooworld {

using namespace std::abi::v1;

struct interface_t : myfoo::interface_t {
  inline static constexpr interface_id_t id{/* "mylib::myfooworld::interface_t" */};

  virtual auto world() noexcept -> void = 0;
};

template<std::derived_from<interface_t> FooWorld>
struct ref_t : myfoo::ref_t<FooWorld> {
  void world() noexcept {
    get_native()->world();
  }
};

} // namespace mylib::myfooworld 

mynewfoo interface - replaces myfoo

namespace mylib::mynewfoo {

using namespace std::abi::v1;

struct interface_t : base::interface_t {
  inline static constexpr interface_id_t id{/* "mylib::mynewfoo::interface_t" */};

  virtual auto helloworld() noexcept -> void = 0;
};

template<std::derived_from<interface_t> NewFoo>
struct ref_t : base::ref_t<NewFoo> {
  void helloworld() noexcept {
    get_native()->helloworld();
  }
};

} // namespace mylib::mynewfoo 

mybar object - latest supporting all the interfaces

namespace mylib::mybar {

using namespace std::abi::v1;

struct object_t {
  inline static constexpr object_id_t id{/* "mylib::mybar::object_t" */};
  using object_t = object::interface_t;
  using arguments_t = base::interface_t;
  using interface_t = myfoo::interface_t;
};

struct implementation_t :
  object_t,
  myfooworld::interface_t, 
  mynewfoo::interface_t, 
  object::interface_t {

  // select_interface can be generated from a list of interface types
  override auto select_interface(interface_id_t desired) noexcept -> select_interface_result_t {
    myfooworld::interface_t* identity = static_cast<myfooworld::interface_t*>(this);
    if (myfooworld::interface_t::id == desired) {
      return implemented<myfooworld::interface_t>(identity);
    } else if (myfoo::interface_t::id == desired) {
      return implemented<myfoo::interface_t>(identity);
    } else if (base::interface_t::id == desired) {
      // casting from 'this' would be ambigous, but casting from 'identity' is not.
      return implemented<base::interface_t>(identity);
    } 
    
    if (mynewfoo::interface_t::id == desired) {
      return implemented<mynewfoo::interface_t>(this);
    }
    
    if (object::interface_t::id == desired) {
      return implemented<object::interface_t>(this);
    }
    
    return {{id, interface_t::id, nullptr}, select_interface_expected_t::not_implemented};
  }

  override auto destruct() noexcept -> void {
    this->~implementation_t();
  }

  // original interface
  override auto hello() noexcept -> void {
    this->myhello();
  }
  // extended interface
  override auto world() noexcept -> void {
    this->myworld();
  }
  // replacement interface
  override auto helloworld() noexcept -> void {
    this->myhelloworld();
  }

private:
  template<class Interface, class Base>
  static auto implemented(Base* base) noexcept -> select_interface_result_t {
    return {{id, Interface::id, static_cast<Interface*>(base)}, select_interface_expected_t::implemented};
  }
  void myhello() noexcept { /**/ }
  void myworld() noexcept { /**/ }
  void myhelloworld() noexcept { /**/ }
};

struct factory_t : factory::interface_t {
  inline static constexpr object_id_t id{/* "mylib::mybar::factory_t" */};

  // select_interface can be generated from a list of interface types
  override auto select_interface(interface_id_t desired) noexcept -> select_interface_result_t {

    if (factory::interface_t::id == desired) {
      return {{id, factory::interface_t::id, static_cast<factory::interface_t*>(this)}, select_interface_expected_t::implemented};
    } else if (base::interface_t::id == desired) {
      return {{id, base::interface_t::id, static_cast<base::interface_t*>(this)}, select_interface_expected_t::implemented};
    }
    
    return {{id, desired, nullptr}, select_interface_expected_t::not_implemented};
  }

  override auto get_storage_info(object_id_t desired, base::interface_t*) noexcept -> storage_info_t {
    if (object_t::id == desired) {
      return {construct_expected_t::implemented, sizeof(implementation_t), alignof(implementation_t)};
    }
    return {construct_expected_t::not_implemented, 0, 0};
  }
  override auto construct(object_id_t desired, storage_t storage, base::interface_t*) noexcept -> construct_result_t {
    if (object_t::id == desired) {
      if (storage.end - storage.begin < sizeof(implementation_t)) { std::terminate(); }
      implementation_t* object = new(storage.begin) implementation_t();
      return {
        construct_expected_t::implemented, 
        object_t::id, object_t::interface_t::id,
        static_cast<object_t::interface_t*>(object), 
        static_cast<object_t::object_t*>(object)};
    }
    return {construct_expected_t::not_implemented, desired, base::interface_t::id, nullptr, nullptr};
  }
};

factory::interface_t get_bar_factory() noexcept {
  return factory_t{};
}

} // namespace mylib::mybar

mybar user

using namespace std::abi::v1;

void use_bar() {
  std::vector<std::byte> storage;
  factory::ref_t<factory::interface_t> fctry = mylib::mybar::get_bar_factory();

  auto info = fctry.get_storage_info<factory::object_t>(ref_empty_object);
  if (!info.has_value()) { return; }
  auto [size, alignment] = info.value();
  storage.resize(size);

  auto object = fctry.construct<factory::object_t>(storage, ref_empty_object);
  if (!object.has_value()) { return; }

  auto newfoo = object.value().select_interface<mylib::mynewfoo::interface_t>();
  if (newfoo.has_value()) {
    newfoo.value().helloworld()
    return; 
  }
  auto fooworld = object.value().select_interface<mylib::myfooworld::interface_t>();
  if (fooworld.has_value()) {
    fooworld.value().hello();
    fooworld.value().world();
    return; 
  }
  auto foo = object.value().select_interface<mylib::myfoo::interface_t>();
  if (foo.has_value()) {
    foo.value().hello();
    return; 
  }
}
//
// maincontext
//
// implementation of the main_context abi using the stdexec::run_loop
//
namespace maincontext {
namespace ex = stdexec;
using namespace std::abi::v1;
using namespace std::execution::abi::v1;
struct receiver_t {
inline static constexpr object_id_t id{/* "execution::main_context::receiver_t" */};
void_exception_ptr_receiver::interface_t* rcvr;
using receiver_concept = ex::receiver_t;
using set_value_t = ex::set_value_t;
STDEXEC_MEMFN_DECL(void set_value)(this receiver_t&& __self) noexcept {
try{
__self.rcvr->set_value();
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("maincontext set_value: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
__self.rcvr->set_error(std::current_exception());
}
}
using set_error_t = ex::set_error_t;
STDEXEC_MEMFN_DECL(void set_error)(this receiver_t&& __self, std::exception_ptr ex) noexcept {
std::print("maincontext set_error: {0}:\n", std::this_thread::get_id());
fflush(stdout);
__self.rcvr->set_error(ex);
}
using set_stopped_t = ex::set_stopped_t;
STDEXEC_MEMFN_DECL(void set_stopped)(this receiver_t&& __self) noexcept {
__self.rcvr->set_stopped();
}
using get_env_t = ex::get_env_t;
STDEXEC_MEMFN_DECL(ex::empty_env get_env)(this receiver_t&& __self) noexcept { return{}; }
};
struct scheduler_t : main_scheduler::interface_t {
inline static constexpr object_id_t id{/* "execution::main_context::scheduler_t" */};
stdexec::run_loop* main;
explicit scheduler_t(stdexec::run_loop* rl) : main(rl) {}
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<main_scheduler::interface_t*>(this);
if (main_scheduler::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
} else if (base::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
}
return {base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto start_schedule_operation(main_scheduler::schedule_operation_t* storage, void_exception_ptr_receiver::interface_t* receiver) noexcept -> void override {
try {
auto schd = main->get_scheduler();
auto item = ex::schedule(schd);
using op_t = ex::__call_result_t<ex::connect_t, decltype(item), maincontext::receiver_t>;
static_assert(sizeof(op_t) <= sizeof(main_scheduler::schedule_operation_t));
auto op = new(storage->data()) op_t(ex::connect(item, maincontext::receiver_t{receiver}));
ex::start(*op);
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("start_schedule_operation: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
receiver->set_error(std::current_exception());
}
}
};
struct maincontext_t : stdexec::__immovable, main_context::interface_t, async_object::interface_t {
inline static constexpr object_id_t id{/* "execution::main_context::maincontext_t" */};
struct maincontext_instance_t : stdexec::__immovable {
maincontext_instance_t() = delete;
explicit maincontext_instance_t(maincontext_t* obj) : main(), destruct(obj), scheduler(&main) {}
struct maincontext_destruct_t : async_object_destruct::interface_t {
inline static constexpr object_id_t id{/* "execution::main_context::maincontext_destruct_t" */};
maincontext_destruct_t() = delete;
explicit maincontext_destruct_t(maincontext_t* obj) : object(obj) {}
maincontext_t* object;
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<async_object_destruct::interface_t*>(this);
if (async_object_destruct::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
} else if (base::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
}
return {base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto start_destruct_operation(void_error_code_receiver::interface_t* receiver) noexcept -> void override {
try {
std::print("main start_destruct_operation: {0}: \n", std::this_thread::get_id());
fflush(stdout);
object->~maincontext_t();
maincontext_t::instance()->reset();
receiver->set_value();
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("main start_destruct_operation: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
receiver->set_error(ec);
}
}
};
stdexec::run_loop main;
maincontext_destruct_t destruct;
scheduler_t scheduler;
};
static auto instance() noexcept -> std::optional<maincontext_instance_t>* {
static std::optional<maincontext_instance_t> inst;
return &inst;
}
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<main_context::interface_t*>(this);
if (main_context::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
} else if (base::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
}
if (async_object::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, static_cast<async_object::interface_t*>(this)};
}
return {base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto get_scheduler() noexcept -> main_scheduler::interface_t* override {
return &instance()->value().scheduler;
}
auto run() noexcept -> void override {
instance()->value().main.run();
}
auto finish() noexcept -> void override {
instance()->value().main.finish();
}
size_t max_concurrency() const noexcept {
return std::thread::hardware_concurrency();
}
auto destruct() noexcept -> async_object_destruct::interface_t* override {
return &instance()->value().destruct;
}
};
inline static main_context::object_t mainstorage{};
inline static maincontext_t* maincontext{};
struct constructor_t : main_constructor::interface_t {
inline static constexpr object_id_t id{/* "execution::main_context::constructor_t" */};
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<main_constructor::interface_t*>(this);
if (main_constructor::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
} else if (base::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
}
return {base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto start_construct_operation(main_context::object_t* storage, async_object_error_code_receiver::interface_t* receiver) noexcept -> void override {
try {
std::print("main start_construct_operation: {0}: \n", std::this_thread::get_id());
fflush(stdout);
static_assert(sizeof(maincontext_t) <= sizeof(main_context::object_t));
maincontext = new(storage->data()) maincontext_t();
maincontext_t::instance()->emplace(maincontext);
receiver->set_value(maincontext);
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("main start_construct_operation: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
receiver->set_error(ec);
}
}
};
} // namespace maincontext
auto std::execution::abi::v1::main_context::get_main_context() noexcept
-> std::execution::abi::v1::main_context::interface_t* {
if (!maincontext::maincontext) { std::terminate(); }
return maincontext::maincontext;
}
inline static maincontext::constructor_t maincontext_constructor{};
auto std::execution::abi::v1::main_context::main_context_construct() noexcept
-> std::execution::abi::v1::main_constructor::interface_t* {
return &maincontext_constructor;
}
//
// mycontext
//
// implementation of the system_context abi using the
// exec::static_thread_pool
//
namespace mycontext {
namespace ex = stdexec;
using namespace std::abi::v1;
using namespace std::execution::abi::v1;
struct receiver_t {
inline static constexpr object_id_t id{/* "execution::system_context::receiver_t" */};
void_error_code_receiver::interface_t* rcvr;
using receiver_concept = ex::receiver_t;
using set_value_t = ex::set_value_t;
STDEXEC_MEMFN_DECL(void set_value)(this receiver_t&& __self) noexcept {
try{
std::print("mycontext set_value: {0}: item\n", std::this_thread::get_id());
fflush(stdout);
__self.rcvr->set_value();
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("mycontext set_value: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
__self.rcvr->set_error(ec);
}
}
using set_error_t = ex::set_error_t;
STDEXEC_MEMFN_DECL(void set_error)(this receiver_t&& __self, std::error_code ec) noexcept {
std::print("mycontext set_error: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
__self.rcvr->set_error(ec);
}
using set_stopped_t = ex::set_stopped_t;
STDEXEC_MEMFN_DECL(void set_stopped)(this receiver_t&& __self) noexcept {
__self.rcvr->set_stopped();
}
using get_env_t = ex::get_env_t;
STDEXEC_MEMFN_DECL(ex::empty_env get_env)(this receiver_t&& __self) noexcept { return{}; }
};
struct scheduler_t : system_scheduler::interface_t {
inline static constexpr object_id_t id{/* "execution::system_context::scheduler_t" */};
exec::static_thread_pool* pool;
explicit scheduler_t(exec::static_thread_pool* pl) : pool(pl) {}
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<system_scheduler::interface_t*>(this);
if (system_scheduler::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
} else if (base::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
}
return {base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto start_schedule_operation(system_scheduler::schedule_operation_t* storage, void_error_code_receiver::interface_t* receiver) noexcept -> void override {
try {
auto schd = pool->get_scheduler();
auto item = ex::schedule(schd);
using op_t = ex::__call_result_t<ex::connect_t, decltype(item), mycontext::receiver_t>;
static_assert(sizeof(op_t) <= sizeof(system_scheduler::schedule_operation_t));
auto op = new(storage->data()) op_t(ex::connect(item, mycontext::receiver_t{receiver}));
ex::start(*op);
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("start_schedule_operation: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
receiver->set_error(ec);
}
}
};
struct context_t : stdexec::__immovable, system_context::interface_t, async_object::interface_t {
inline static constexpr object_id_t id{/* "execution::system_context::context_t" */};
struct context_instance_t : stdexec::__immovable {
context_instance_t() = delete;
explicit context_instance_t(context_t* obj) : pool(std::thread::hardware_concurrency()), destruct(obj), scheduler(&pool) {}
struct context_destruct_t : async_object_destruct::interface_t {
inline static constexpr object_id_t id{/* "execution::system_context::context_destruct_t" */};
context_destruct_t() = delete;
explicit context_destruct_t(context_t* obj) : object(obj) {}
context_t* object;
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<async_object_destruct::interface_t*>(this);
if (async_object_destruct::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
} else if (base::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
}
return {base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto start_destruct_operation(void_error_code_receiver::interface_t* receiver) noexcept -> void override {
try {
std::print("system start_destruct_operation: {0}: \n", std::this_thread::get_id());
fflush(stdout);
object->~context_t();
context_t::instance()->reset();
receiver->set_value();
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("system start_destruct_operation: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
receiver->set_error(ec);
}
}
};
exec::static_thread_pool pool;
context_destruct_t destruct;
scheduler_t scheduler;
};
static auto instance() noexcept -> std::optional<context_instance_t>* {
static std::optional<context_instance_t> inst;
return &inst;
}
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<system_context::interface_t*>(this);
if (system_context::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
} else if (base::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
}
if (async_object::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, static_cast<async_object::interface_t*>(this)};
}
return {base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto get_scheduler() noexcept -> system_scheduler::interface_t* override {
return &instance()->value().scheduler;
}
size_t max_concurrency() const noexcept {
return std::thread::hardware_concurrency();
}
auto destruct() noexcept -> async_object_destruct::interface_t* override {
return &instance()->value().destruct;
}
};
inline static system_context::object_t storage{};
inline static context_t* context{};
struct constructor_t : system_constructor::interface_t {
inline static constexpr object_id_t id{/* "execution::system_context::constructor_t" */};
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<system_constructor::interface_t*>(this);
if (system_constructor::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
} else if (base::interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, desired, identity};
}
return {base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto start_construct_operation(system_context::object_t* storage, async_object_error_code_receiver::interface_t* receiver) noexcept -> void override {
try {
std::print("system start_construct_operation: {0}: \n", std::this_thread::get_id());
fflush(stdout);
static_assert(sizeof(context_t) <= sizeof(system_context::object_t));
context = new(storage->data()) context_t();
context_t::instance()->emplace(context);
receiver->set_value(context);
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("system start_construct_operation: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
receiver->set_error(ec);
}
}
};
} // namespace mycontext
auto std::execution::abi::v1::system_context::get_system_context() noexcept
-> std::execution::abi::v1::system_context::interface_t* {
if (!mycontext::context) { std::terminate(); }
return mycontext::context;
}
inline static mycontext::constructor_t context_constructor{};
auto std::execution::abi::v1::system_context::system_context_construct() noexcept
-> std::execution::abi::v1::system_constructor::interface_t* {
return &context_constructor;
}
//
// ::abi::
//
// types and namespaces under ::abi::
// declare the interfaces and objects that describe
// a stable, versioned, extensible, replaceable, ABI
//
//
// std::abi::v1
//
// this defines the interface_id and object_id structs.
// each has its own namespace uuid and ensures that
// comparisons do not work across object and interface ids
//
namespace std::abi::v1 {
using cthash::uuid;
struct interface_uuid : uuid {
using super = uuid;
using super::super;
constexpr auto time_high() const noexcept {
return cthash::utility::little_endian_bytes_to<uint32_t>(super::member<TIME_HIGH>());
}
constexpr auto time_low() const noexcept {
return cthash::utility::little_endian_bytes_to<uint16_t>(super::member<TIME_LOW>());
}
constexpr auto reserved() const noexcept {
return cthash::utility::little_endian_bytes_to<uint16_t>(super::member<RESERVED>());
}
constexpr auto family() const noexcept {
return cthash::utility::big_endian_bytes_to<uint8_t>(super::member<FAMILY>());
}
constexpr auto node() const noexcept {
return cthash::utility::big_endian_bytes_to<uint64_t>(super::member<NODE>());
}
};
consteval auto operator"" _interface_uuid(const char * data, size_t length) {
return interface_uuid{};
}
struct object_uuid : uuid {
using super = uuid;
using super::super;
constexpr auto time_high() const noexcept {
return cthash::utility::little_endian_bytes_to<uint32_t>(super::member<TIME_HIGH>());
}
constexpr auto time_low() const noexcept {
return cthash::utility::little_endian_bytes_to<uint16_t>(super::member<TIME_LOW>());
}
constexpr auto reserved() const noexcept {
return cthash::utility::little_endian_bytes_to<uint16_t>(super::member<RESERVED>());
}
constexpr auto family() const noexcept {
return cthash::utility::big_endian_bytes_to<uint8_t>(super::member<FAMILY>());
}
constexpr auto node() const noexcept {
return cthash::utility::big_endian_bytes_to<uint64_t>(super::member<NODE>());
}
};
consteval auto operator"" _object_uuid(const char * data, size_t length) {
return object_uuid{};
}
struct interface_id_t {
inline static constexpr uuid namespace_id = "31e5da35-7745-46f4-a241-7cac52889433"_uuid_v2;
// constexpr interface_id_t() = delete;
// explicit constexpr interface_id_t(const char* name) : value(/*make uuid_v5 */) { }
interface_uuid value;
friend auto operator<=>(const interface_id_t&, const interface_id_t&) = default;
};
struct object_id_t {
inline static constexpr uuid namespace_id = "584fc8d0-15ac-4c43-a5be-07b4e9f68dc3"_uuid_v2;
// object_id_t() = delete;
// explicit object_id_t(const char* name) : value(/*make uuid_v5 */) { }
object_uuid value;
friend auto operator<=>(const object_id_t&, const object_id_t&) = default;
};
template<class Interface>
struct interface_t : interface_id_t {
using interface_type = Interface;
friend auto operator<=>(const interface_t&, const interface_t&) = default;
};
template<class Object>
struct object_t : object_id_t {
using object_type = Object;
friend auto operator<=>(const object_t&, const object_t&) = default;
};
} // namespace std::abi::v1
//
// std::abi::v1::base
//
// this defines the abi for the base interface.
//
namespace std::abi::v1::base {
// all the expected exit states for the select_interface function
enum class select_interface_expected_t : std::uint8_t {
select_interface_expected_uninitialized = 0,
not_implemented = 1,
implemented = 2
};
struct interface_t;
// the result of the select_interface function
struct select_interface_result_t {
// discriminator
select_interface_expected_t expected;
// unique identifier for the object that
// implements the select_interface that produced the result
// always valid
object_id_t object_id;
// unique identifier for the interface that
// was given to select_interface
// always valid
interface_id_t interface_id;
// stores base interface of the interface that matches @interface_id
// this is valid to static_cast<>() to the interface that matches @interface_id
// stores a valid pointer when @expected == select_interface_expected_t::implemented
// stores a null pointer when @expected == select_interface_expected_t::not_implemented
interface_t* value;
};
struct interface_t {
inline static constexpr abi::v1::interface_t<interface_t> id{"base::interface_t"_interface_uuid};
virtual ~interface_t() {}
// select_interface()
//
// This function is a dynamic cast between interfaces.
// Each interface has a unique uuid that is used to
// carry the type info across the abi boundary.
//
// @param interface_id The uuid of the interface that the
// caller is asking to be returned
//
// @returns
//
// If the object implements the interface that matches the given uuid,
// the result will contain a pointer to a base of the interface that
// matches the given uuid
//
// If the object does not implement the interface that matches the
// given uuid, the result will have a null pointer
virtual auto select_interface(interface_id_t) noexcept -> select_interface_result_t = 0;
};
// this provides a base for ref_t<> types for interfaces
// stores the interface pointer and the object_id of the
// object that implemented this interface
//
// derived ref_t<> type use the get_native() method to
// implement C++ wrapper methods for the ABI methods
//
// ref_t do not support being 'empty'
template<class Interface>
struct interface_ref_t {
using interface_t = Interface;
~interface_ref_t() noexcept { value = nullptr; }
interface_ref_t() = delete;
explicit interface_ref_t(object_id_t oid, Interface* i) noexcept :
object_id(oid),
value(i) {
if (!i) { std::terminate(); }
}
interface_ref_t(const interface_ref_t&) = default;
interface_ref_t& operator=(const interface_ref_t&) = default;
interface_ref_t(interface_ref_t&& o) noexcept :
object_id(o.object_id),
value(std::exchange(o.value, nullptr)) {
}
interface_ref_t& operator=(interface_ref_t&& o) noexcept {
object_id = o.object_id;
value = std::exchange(o.value, nullptr);
}
Interface& get_native() const noexcept {
return value;
}
object_id_t get_object_id() const noexcept {
return object_id;
}
private:
object_id_t object_id;
interface_t* value;
};
// ref_t<> type for the base interface
//
// implements a C++ wrapper for select_interface
// that hides the ABI
//
// does not support being 'empty'
template<std::derived_from<interface_t> Interface>
struct ref_t : interface_ref_t<Interface> {
using interface_t = Interface;
inline static constexpr interface_id_t id{interface_t::id};
// select_interface()
//
// implements select_interface as a cast between ref_t.
// auto my = base.select_interface<MyInterface>();
// my.value().mymethod();
//
// converts the ABI result struct into a std::expected
// containing a ref_t of the new interface
template<std::derived_from<interface_t> DesiredInterface>
auto select_interface() const noexcept
-> std::expected<
ref_t<DesiredInterface>,
std::tuple<interface_id_t, object_id_t>> {
select_interface_result_t result = this->get_native().select_interface(DesiredInterface::id);
if (select_interface_expected_t::implemented != result.expected) {
return std::make_tuple(DesiredInterface::id, result.object_id);
}
return ref_t<DesiredInterface>{result.object_id, static_cast<DesiredInterface*>(result.value)};
}
};
} // namespace std::abi::v1::base
//
// std::execution::abi::v1::void_error_code_receiver
//
// this defines the abi for void_error_code_receiver.
//
// auto set_value() noexcept -> void
// auto set_error(std::error_code) noexcept -> void
// auto set_stopped() noexcept -> void
//
namespace std::execution::abi::v1::void_error_code_receiver {
using namespace std::abi::v1;
struct interface_t : base::interface_t {
inline static constexpr std::abi::v1::interface_t<interface_t> id{"execution::void_error_code_receiver::interface_t"_interface_uuid};
virtual auto set_value() noexcept -> void = 0;
virtual auto set_error(std::error_code) noexcept -> void = 0;
virtual auto set_stopped() noexcept -> void = 0;
};
template<std::derived_from<interface_t> Receiver>
struct ref_t : base::ref_t<Receiver> {
auto set_value() noexcept -> void {
this->get_native().set_value();
}
auto set_error(std::error_code ec) noexcept -> void {
this->get_native().set_error(ec);
}
auto set_stopped() noexcept -> void {
this->get_native().set_stopped();
}
};
} // namespace std::execution::abi::v1::void_error_code_receiver
//
// std::execution::abi::v1::void_exception_ptr_receiver
//
// this defines the abi for void_exception_ptr_receiver.
//
// auto set_value() noexcept -> void
// auto set_error(std::exception_ptr) noexcept -> void
// auto set_stopped() noexcept -> void
//
namespace std::execution::abi::v1::void_exception_ptr_receiver {
using namespace std::abi::v1;
struct interface_t : base::interface_t {
inline static constexpr std::abi::v1::interface_t<interface_t> id{"execution::void_exception_ptr_receiver::interface_t"_interface_uuid};
virtual auto set_value() noexcept -> void = 0;
virtual auto set_error(std::exception_ptr) noexcept -> void = 0;
virtual auto set_stopped() noexcept -> void = 0;
};
template<std::derived_from<interface_t> Receiver>
struct ref_t : base::ref_t<Receiver> {
auto set_value() noexcept -> void {
this->get_native().set_value();
}
auto set_error(std::error_code ec) noexcept -> void {
this->get_native().set_error(ec);
}
auto set_stopped() noexcept -> void {
this->get_native().set_stopped();
}
};
} // namespace std::execution::abi::v1::void_exception_ptr_receiver
//
// std::execution::abi::v1::async_object_destruct
//
// this defines the abi for async_object_destruct.
// an async_object_destruct starts the async destruct operation
// for an async object
//
// auto start_destruct_operation(
// void_error_code_receiver::interface_t* receiver) noexcept -> void
//
namespace std::execution::abi::v1::async_object_destruct {
using namespace std::abi::v1;
struct interface_t : base::interface_t {
inline static constexpr std::abi::v1::interface_t<interface_t> id{"execution::async_object_destructor::interface_t"_interface_uuid};
// start_destruct_operation()
//
// This function starts the async destruct operation
// for an async object.
//
// @param receiver The receiver to invoke
// once the async destruct operation has completed
//
virtual auto start_destruct_operation(
void_error_code_receiver::interface_t* receiver) noexcept -> void = 0;
};
template<std::derived_from<interface_t> async_object>
struct ref_t : base::ref_t<async_object> {
auto start_destruct_operation(void_error_code_receiver::ref_t<void_error_code_receiver::interface_t> receiver) noexcept -> void {
this->get_native().start_destruct_operation(&receiver.get_native());
}
};
} // namespace std::execution::abi::v1::async_object_destruct
//
// std::execution::abi::v1::async_object
//
// this defines the abi for async_object.
// an async_object is the result of an async
// construct operation
//
// auto destruct() noexcept
// -> async_object_destruct::interface_t*
//
namespace std::execution::abi::v1::async_object {
using namespace std::abi::v1;
struct interface_t : base::interface_t {
inline static constexpr std::abi::v1::interface_t<interface_t> id{"execution::async_object::interface_t"_interface_uuid};
// destruct()
//
// This is an async function that destructs the async object.
//
// @returns an interface that is used to start the async destruct operation
//
virtual auto destruct() noexcept
-> async_object_destruct::interface_t* = 0;
};
template<std::derived_from<interface_t> async_object>
struct ref_t : base::ref_t<async_object> {
auto destruct() noexcept
-> async_object_destruct::ref_t<async_object_destruct::interface_t> {
return async_object_destruct::ref_t<async_object_destruct::interface_t>{
this->get_object_id(), this->get_native().destruct()};
}
};
} // namespace std::execution::abi::v1::async_object
//
// std::execution::abi::v1::async_object_error_code_receiver
//
// this defines the abi for async_object_error_code_receiver.
//
// auto set_value(async_object::interface_t*) noexcept -> void
// auto set_error(std::error_code) noexcept -> void
// auto set_stopped() noexcept -> void
//
namespace std::execution::abi::v1::async_object_error_code_receiver {
using namespace std::abi::v1;
struct interface_t : base::interface_t {
inline static constexpr std::abi::v1::interface_t<interface_t> id{"execution::object_error_code_receiver::interface_t"_interface_uuid};
virtual auto set_value(async_object::interface_t*) noexcept -> void = 0;
virtual auto set_error(std::error_code) noexcept -> void = 0;
virtual auto set_stopped() noexcept -> void = 0;
};
template<std::derived_from<interface_t> Receiver>
struct ref_t : base::ref_t<Receiver> {
auto set_value(async_object::ref_t<async_object::interface_t> obj) noexcept -> void {
this->get_native().set_value(&obj.get_native());
}
auto set_error(std::error_code ec) noexcept -> void {
this->get_native().set_error(ec);
}
auto set_stopped() noexcept -> void {
this->get_native().set_stopped();
}
};
} // namespace std::execution::abi::v1::async_object_error_code_receiver
//
// std::execution::abi::v1::main_scheduler
//
// this defines the abi for main_scheduler.
//
// struct schedule_operation_t
//
// auto start_schedule_operation(schedule_operation_t* storage, void_exception_ptr_receiver::interface_t* receiver) noexcept -> void
//
namespace std::execution::abi::v1::main_scheduler {
using namespace std::abi::v1;
// schedule_operation_t sets the max size of the main schedule operation
// object
struct schedule_operation_t : std::array<std::byte, sizeof(void*)*8> {
inline static constexpr object_id_t id{/* "execution::main_scheduler::schedule_operation_t" */};
};
struct interface_t : base::interface_t {
inline static constexpr std::abi::v1::interface_t<interface_t> id{"execution::main_scheduler::interface_t"_interface_uuid};
virtual auto start_schedule_operation(schedule_operation_t* storage, void_exception_ptr_receiver::interface_t* receiver) noexcept -> void = 0;
};
template<std::derived_from<interface_t> Scheduler>
struct ref_t : base::ref_t<Scheduler> {
auto start_schedule_operation(schedule_operation_t* storage, void_exception_ptr_receiver::interface_t* receiver) noexcept -> void {
this->get_native().start_schedule_operation(storage, receiver);
}
};
} // namespace std::execution::abi::v1::main_scheduler
namespace std::execution::abi::v1::main_constructor {
struct interface_t;
} // namespace std::execution::abi::v1::main_constructor
//
// std::execution::abi::v1::main_context
//
// this defines the abi for main_context.
//
// struct object_t
//
// auto get_scheduler() noexcept -> main_scheduler::interface_t*
//
namespace std::execution::abi::v1::main_context {
using namespace std::abi::v1;
// object_t sets the max size of the main context
// object
struct object_t : std::array<std::byte, sizeof(void*) * 4> {
inline static constexpr object_id_t id{/* "execution::main_context::object_t" */};
};
struct interface_t : base::interface_t {
inline static constexpr std::abi::v1::interface_t<interface_t> id{"execution::main_scheduler::interface_t"_interface_uuid};
virtual auto get_scheduler() noexcept -> main_scheduler::interface_t* = 0;
virtual auto run() noexcept -> void = 0;
virtual auto finish() noexcept -> void = 0;
};
template<std::derived_from<interface_t> Context>
struct ref_t : base::ref_t<Context> {
auto get_scheduler() noexcept -> system_scheduler::ref_t<system_scheduler::interface_t> {
return {object_t::id, this->get_native().get_scheduler()};
}
auto run() noexcept -> void {
this->get_native().run();
}
auto finish() noexcept -> void {
this->get_native().finish();
}
};
extern interface_t* get_main_context() noexcept;
extern auto main_context_construct() noexcept
-> std::execution::abi::v1::main_constructor::interface_t*;
} // namespace std::execution::abi::v1::main_context
//
// std::execution::abi::v1::main_constructor
//
// this defines the abi for main_constructor.
// the storage for the main constructor operation is reserved
// in main_context::object_t
//
// auto start_construct_operation(main_context::object_t* storage, async_object_error_code_receiver::interface_t* receiver) noexcept -> void
//
namespace std::execution::abi::v1::main_constructor {
using namespace std::abi::v1;
struct interface_t : base::interface_t {
inline static constexpr std::abi::v1::interface_t<interface_t> id{"execution::main_constructor::interface_t"_interface_uuid};
virtual auto start_construct_operation(main_context::object_t* storage, async_object_error_code_receiver::interface_t* receiver) noexcept -> void = 0;
};
template<std::derived_from<interface_t> Scheduler>
struct ref_t : base::ref_t<Scheduler> {
auto start_constructor_operation(main_context::object_t* storage, void_error_code_receiver::interface_t* receiver) noexcept -> void {
this->get_native().start_constructor_operation(storage, receiver);
}
};
} // namespace std::execution::abi::v1::system_constructor
//
// std::execution::abi::v1::system_scheduler
//
// this defines the abi for system_scheduler.
//
// struct schedule_operation_t
//
// auto start_schedule_operation(schedule_operation_t* storage, void_error_code_receiver::interface_t* receiver) noexcept -> void
//
namespace std::execution::abi::v1::system_scheduler {
using namespace std::abi::v1;
// schedule_operation_t sets the max size of the system schedule operation
// object
struct schedule_operation_t : std::array<std::byte, sizeof(void*)*8> {
inline static constexpr object_id_t id{/* "execution::system_scheduler::schedule_operation_t" */};
};
struct interface_t : base::interface_t {
inline static constexpr std::abi::v1::interface_t<interface_t> id{"execution::system_scheduler::interface_t"_interface_uuid};
virtual auto start_schedule_operation(schedule_operation_t* storage, void_error_code_receiver::interface_t* receiver) noexcept -> void = 0;
};
template<std::derived_from<interface_t> Scheduler>
struct ref_t : base::ref_t<Scheduler> {
auto start_schedule_operation(schedule_operation_t* storage, void_error_code_receiver::interface_t* receiver) noexcept -> void {
this->get_native().start_schedule_operation(storage, receiver);
}
};
} // namespace std::execution::abi::v1::system_scheduler
namespace std::execution::abi::v1::system_constructor {
struct interface_t;
} // namespace std::execution::abi::v1::system_constructor
//
// std::execution::abi::v1::system_context
//
// this defines the abi for system_context.
//
// struct object_t
//
// auto get_scheduler() noexcept -> system_scheduler::interface_t*
//
namespace std::execution::abi::v1::system_context {
using namespace std::abi::v1;
// object_t sets the max size of the system context
// object
struct object_t : std::array<std::byte, sizeof(void*) * 4> {
inline static constexpr object_id_t id{/* "execution::system_context::object_t" */};
};
struct interface_t : base::interface_t {
inline static constexpr std::abi::v1::interface_t<interface_t> id{"execution::system_scheduler::interface_t"_interface_uuid};
virtual auto get_scheduler() noexcept -> system_scheduler::interface_t* = 0;
};
template<std::derived_from<interface_t> Context>
struct ref_t : base::ref_t<Context> {
auto get_scheduler() noexcept -> system_scheduler::ref_t<system_scheduler::interface_t> {
return {object_t::id, this->get_native().get_scheduler()};
}
};
extern interface_t* get_system_context() noexcept;
extern auto system_context_construct() noexcept
-> std::execution::abi::v1::system_constructor::interface_t*;
} // namespace std::execution::abi::v1::system_context
//
// std::execution::abi::v1::system_constructor
//
// this defines the abi for system_constructor.
// the storage for the system constructor operation is reserved
// in system_context::object_t
//
// auto start_construct_operation(system_context::object_t* storage, async_object_error_code_receiver::interface_t* receiver) noexcept -> void
//
namespace std::execution::abi::v1::system_constructor {
using namespace std::abi::v1;
struct interface_t : base::interface_t {
inline static constexpr std::abi::v1::interface_t<interface_t> id{"execution::system_constructor::interface_t"_interface_uuid};
virtual auto start_construct_operation(system_context::object_t* storage, async_object_error_code_receiver::interface_t* receiver) noexcept -> void = 0;
};
template<std::derived_from<interface_t> Scheduler>
struct ref_t : base::ref_t<Scheduler> {
auto start_constructor_operation(system_context::object_t* storage, void_error_code_receiver::interface_t* receiver) noexcept -> void {
this->get_native().start_constructor_operation(storage, receiver);
}
};
} // namespace std::execution::abi::v1::system_constructor
//
// std::execution::v1
//
// implementation of async_object types that wrap the
// std::execution::abi::v1
// the types are for use with the stdexec library
//
namespace std::execution::v1 {
namespace ex = stdexec;
//
// async_object_destruct_receiver
//
// implements the receiver abi to wrap a stdexec receiver
//
template<class Receiver>
struct async_object_destruct_receiver : std::execution::abi::v1::void_error_code_receiver::interface_t {
inline static constexpr std::abi::v1::object_id_t id{/* "execution::async_object_destruct_receiver::object_t" */};
Receiver rcvr;
explicit async_object_destruct_receiver(Receiver&& receiver) : rcvr(std::move(receiver)) {}
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<std::execution::abi::v1::void_error_code_receiver::interface_t*>(this);
if (std::execution::abi::v1::void_error_code_receiver::interface_t::id == desired) {
return {std::abi::v1::base::select_interface_expected_t::implemented, id, desired, identity};
} else if (std::abi::v1::base::interface_t::id == desired) {
return {std::abi::v1::base::select_interface_expected_t::implemented, id, desired, identity};
}
return {std::abi::v1::base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto set_value() noexcept -> void override {
try {
ex::set_value(std::move(rcvr));
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("async_object_destruct_receiver set_value: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
ex::set_error(std::move(rcvr), ec);
}
}
auto set_error(std::error_code ec) noexcept -> void override {
ex::set_error(std::move(rcvr), ec);
}
auto set_stopped() noexcept -> void override {
ex::set_stopped(std::move(rcvr));
}
};
//
// async_object_destruct_operation
//
// implements the stdexec wrapper for an operation over the abi
//
template<class Receiver>
struct async_object_destruct_operation : ex::__immovable {
explicit async_object_destruct_operation(
Receiver&& receiver,
std::execution::abi::v1::async_object_destruct::interface_t* object)
: rcvr(std::move(receiver)), obj(object) {}
async_object_destruct_receiver<Receiver> rcvr;
std::execution::abi::v1::async_object_destruct::interface_t* obj;
using start_t = ex::start_t;
STDEXEC_MEMFN_DECL(auto start)(this async_object_destruct_operation& __self) noexcept -> void {
try{
__self.obj->start_destruct_operation(&__self.rcvr);
} catch (const std::system_error& ex) {
auto ec = ex.code();
std::print("async_object_destruct_operation start: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
__self.rcvr.set_error(ec);
}
}
};
//
// async_object_destructor
//
// implements the stdexec wrapper for a sender over the abi
//
struct async_object_destructor {
using sender_concept = ex::sender_t;
std::execution::abi::v1::async_object_destruct::interface_t* object;
using get_completion_signatures_t = ex::get_completion_signatures_t;
template<class Env>
STDEXEC_MEMFN_DECL(auto get_completion_signatures)(this async_object_destructor, Env&& ) noexcept
-> ex::completion_signatures<
ex::set_value_t(),
ex::set_error_t(std::error_code),
ex::set_stopped_t()> {}
using connect_t = ex::connect_t;
template<class Receiver>
STDEXEC_MEMFN_DECL(auto connect)(this async_object_destructor __self, Receiver&& receiver) noexcept -> async_object_destruct_operation<std::decay_t<Receiver>> {
return async_object_destruct_operation<std::decay_t<Receiver>>{static_cast<Receiver&&>(receiver), __self.object};
}
};
//
// async_object
//
// implements a C++ wrapper for an async_object over the abi
//
struct async_object {
std::execution::abi::v1::async_object::interface_t* object;
auto destruct() noexcept -> async_object_destructor {
return async_object_destructor{object->destruct()};
}
};
} // namespace std::execution::v1
//
// std::execution::v1
//
// implementation of main_context types that wrap the
// std::execution::abi::v1
// the types are for use with the stdexec library
//
namespace std::execution::v1 {
namespace ex = stdexec;
template<class Receiver>
struct main_schedule_receiver : std::execution::abi::v1::void_exception_ptr_receiver::interface_t {
inline static constexpr std::abi::v1::object_id_t id{/* "execution::main_schedule_receiver::object_t" */};
Receiver rcvr;
explicit main_schedule_receiver(Receiver&& receiver) : rcvr(std::move(receiver)) {}
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<std::execution::abi::v1::void_exception_ptr_receiver::interface_t*>(this);
if (std::execution::abi::v1::void_exception_ptr_receiver::interface_t::id == desired) {
return {std::abi::v1::base::select_interface_expected_t::implemented, id, desired, identity};
} else if (std::abi::v1::base::interface_t::id == desired) {
return {std::abi::v1::base::select_interface_expected_t::implemented, id, desired, identity};
}
return {std::abi::v1::base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto set_value() noexcept -> void override {
try {
ex::set_value(std::move(rcvr));
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("main_schedule_receiver set_value: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
ex::set_error(std::move(rcvr), ec);
}
}
auto set_error(std::exception_ptr ex) noexcept -> void override {
ex::set_error(std::move(rcvr), ex);
}
auto set_stopped() noexcept -> void override {
ex::set_stopped(std::move(rcvr));
}
};
template<class Receiver>
struct main_scheduler_operation : ex::__immovable {
explicit main_scheduler_operation(Receiver&& receiver) : rcvr(std::move(receiver)) {}
main_schedule_receiver<Receiver> rcvr;
std::execution::abi::v1::main_scheduler::schedule_operation_t op;
using start_t = ex::start_t;
STDEXEC_MEMFN_DECL(auto start)(this main_scheduler_operation& __self) noexcept -> void {
try{
auto ctx = std::execution::abi::v1::main_context::get_main_context();
auto sch = ctx->get_scheduler();
sch->start_schedule_operation(&__self.op, &__self.rcvr);
} catch (const std::system_error& ex) {
auto ec = ex.code();
std::print("main_scheduler_operation start: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
__self.rcvr.set_error(std::current_exception());
}
}
};
struct main_scheduler_domain : stdexec::default_domain {
};
struct main_scheduler {
struct main_schedule_sender {
using sender_concept = ex::sender_t;
using get_completion_signatures_t = ex::get_completion_signatures_t;
template<class Env>
STDEXEC_MEMFN_DECL(auto get_completion_signatures)(this main_schedule_sender, Env&& ) noexcept
-> ex::completion_signatures<
ex::set_value_t(),
ex::set_error_t(std::error_code),
ex::set_stopped_t()> {}
using connect_t = ex::connect_t;
template<class Receiver>
STDEXEC_MEMFN_DECL(auto connect)(this main_schedule_sender, Receiver&& receiver) noexcept -> main_scheduler_operation<std::decay_t<Receiver>> {
return main_scheduler_operation<std::decay_t<Receiver>>{static_cast<Receiver&&>(receiver)};
}
struct env {
template <class _CPO>
STDEXEC_MEMFN_DECL(auto query)(this const env& __self, get_completion_scheduler_t<_CPO>) noexcept
-> main_scheduler {
return {};
}
};
using get_env_t = ex::get_env_t;
STDEXEC_MEMFN_DECL(auto get_env)(this const main_schedule_sender& __self) noexcept
-> env {
return{};
}
};
using schedule_t = ex::schedule_t;
STDEXEC_MEMFN_DECL(auto schedule)(this main_scheduler) noexcept -> main_schedule_sender {
return {};
}
STDEXEC_MEMFN_DECL(auto query)(this const main_scheduler&, stdexec::get_forward_progress_guarantee_t) noexcept
-> stdexec::forward_progress_guarantee {
return stdexec::forward_progress_guarantee::concurrent;
}
STDEXEC_MEMFN_DECL(auto query)(this const main_scheduler&, stdexec::get_domain_t) noexcept
-> main_scheduler_domain {
return {};
}
friend auto operator<=>(const main_scheduler&, const main_scheduler&) = default;
};
struct main_context : stdexec::__immovable {
auto get_scheduler() const noexcept -> main_scheduler {
return {};
}
void run() noexcept {
auto ctx = std::execution::abi::v1::main_context::get_main_context();
ctx->run();
}
void finish() noexcept {
auto ctx = std::execution::abi::v1::main_context::get_main_context();
ctx->finish();
}
size_t max_concurrency() const noexcept {
return 1;
}
};
template<class Receiver>
struct main_construct_receiver : std::execution::abi::v1::async_object_error_code_receiver::interface_t {
inline static constexpr std::abi::v1::object_id_t id{/* "execution::main_construct_receiver::object_t" */};
Receiver rcvr;
explicit main_construct_receiver(Receiver&& receiver) : rcvr(std::move(receiver)) {}
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<std::execution::abi::v1::async_object_error_code_receiver::interface_t*>(this);
if (std::execution::abi::v1::async_object_error_code_receiver::interface_t::id == desired) {
return {std::abi::v1::base::select_interface_expected_t::implemented, id, desired, identity};
} else if (std::abi::v1::base::interface_t::id == desired) {
return {std::abi::v1::base::select_interface_expected_t::implemented, id, desired, identity};
}
return {std::abi::v1::base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto set_value(std::execution::abi::v1::async_object::interface_t* object) noexcept -> void override {
try {
ex::set_value(std::move(rcvr), async_object{object});
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("main_construct_receiver set_value: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
ex::set_error(std::move(rcvr), ec);
}
}
auto set_error(std::error_code ec) noexcept -> void override {
ex::set_error(std::move(rcvr), ec);
}
auto set_stopped() noexcept -> void override {
ex::set_stopped(std::move(rcvr));
}
};
template<class Receiver>
struct main_construct_operation : ex::__immovable {
explicit main_construct_operation(Receiver&& receiver, std::execution::abi::v1::main_context::object_t* storage) : rcvr(std::move(receiver)), stg(storage) {}
main_construct_receiver<Receiver> rcvr;
std::execution::abi::v1::main_context::object_t* stg;
using start_t = ex::start_t;
STDEXEC_MEMFN_DECL(auto start)(this main_construct_operation& __self) noexcept -> void {
try{
auto construct = std::execution::abi::v1::main_context::main_context_construct();
construct->start_construct_operation(__self.stg, &__self.rcvr);
} catch (const std::system_error& ex) {
auto ec = ex.code();
std::print("main_construct_operation start: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
__self.rcvr.set_error(ec);
}
}
};
struct main_context_constructor {
using sender_concept = ex::sender_t;
std::execution::abi::v1::main_context::object_t* storage;
using get_completion_signatures_t = ex::get_completion_signatures_t;
template<class Env>
STDEXEC_MEMFN_DECL(auto get_completion_signatures)(this main_context_constructor, Env&& ) noexcept
-> ex::completion_signatures<
ex::set_value_t(async_object),
ex::set_error_t(std::error_code),
ex::set_stopped_t()> {}
using connect_t = ex::connect_t;
template<class Receiver>
STDEXEC_MEMFN_DECL(auto connect)(this main_context_constructor __self, Receiver&& receiver) noexcept -> main_construct_operation<std::decay_t<Receiver>> {
return main_construct_operation<std::decay_t<Receiver>>{static_cast<Receiver&&>(receiver), __self.storage};
}
};
auto get_main_context() noexcept -> main_context {
return {};
}
auto main_context_construct(std::execution::abi::v1::main_context::object_t* storage) noexcept -> main_context_constructor {
return {storage};
}
} // namespace std::execution::v1
//
// std::execution::v1
//
// implementation of system_context types that wrap the
// std::execution::abi::v1
// the types are for use with the stdexec library
//
namespace std::execution::v1 {
namespace ex = stdexec;
template<class Receiver>
struct system_schedule_receiver : std::execution::abi::v1::void_error_code_receiver::interface_t {
inline static constexpr std::abi::v1::object_id_t id{/* "execution::system_schedule_receiver::object_t" */};
Receiver rcvr;
explicit system_schedule_receiver(Receiver&& receiver) : rcvr(std::move(receiver)) {}
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<std::execution::abi::v1::void_error_code_receiver::interface_t*>(this);
if (std::execution::abi::v1::void_error_code_receiver::interface_t::id == desired) {
return {std::abi::v1::base::select_interface_expected_t::implemented, id, desired, identity};
} else if (std::abi::v1::base::interface_t::id == desired) {
return {std::abi::v1::base::select_interface_expected_t::implemented, id, desired, identity};
}
return {std::abi::v1::base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto set_value() noexcept -> void override {
try {
ex::set_value(std::move(rcvr));
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("system_schedule_receiver set_value: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
ex::set_error(std::move(rcvr), ec);
}
}
auto set_error(std::error_code ec) noexcept -> void override {
ex::set_error(std::move(rcvr), ec);
}
auto set_stopped() noexcept -> void override {
ex::set_stopped(std::move(rcvr));
}
};
template<class Receiver>
struct system_scheduler_operation : ex::__immovable {
explicit system_scheduler_operation(Receiver&& receiver) : rcvr(std::move(receiver)) {}
system_schedule_receiver<Receiver> rcvr;
std::execution::abi::v1::system_scheduler::schedule_operation_t op;
using start_t = ex::start_t;
STDEXEC_MEMFN_DECL(auto start)(this system_scheduler_operation& __self) noexcept -> void {
try{
auto ctx = std::execution::abi::v1::system_context::get_system_context();
auto sch = ctx->get_scheduler();
sch->start_schedule_operation(&__self.op, &__self.rcvr);
} catch (const std::system_error& ex) {
auto ec = ex.code();
std::print("system_scheduler_operation start: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
__self.rcvr.set_error(ec);
}
}
};
struct system_scheduler_domain : stdexec::default_domain {
};
struct system_scheduler {
struct system_schedule_sender {
using sender_concept = ex::sender_t;
using get_completion_signatures_t = ex::get_completion_signatures_t;
template<class Env>
STDEXEC_MEMFN_DECL(auto get_completion_signatures)(this system_schedule_sender, Env&& ) noexcept
-> ex::completion_signatures<
ex::set_value_t(),
ex::set_error_t(std::error_code),
ex::set_stopped_t()> {}
using connect_t = ex::connect_t;
template<class Receiver>
STDEXEC_MEMFN_DECL(auto connect)(this system_schedule_sender, Receiver&& receiver) noexcept -> system_scheduler_operation<std::decay_t<Receiver>> {
return system_scheduler_operation<std::decay_t<Receiver>>{static_cast<Receiver&&>(receiver)};
}
struct env {
template <class _CPO>
STDEXEC_MEMFN_DECL(auto query)(this const env& __self, get_completion_scheduler_t<_CPO>) noexcept
-> system_scheduler {
return {};
}
};
using get_env_t = ex::get_env_t;
STDEXEC_MEMFN_DECL(auto get_env)(this const system_schedule_sender& __self) noexcept
-> env {
return{};
}
};
using schedule_t = ex::schedule_t;
STDEXEC_MEMFN_DECL(auto schedule)(this system_scheduler) noexcept -> system_schedule_sender {
return {};
}
STDEXEC_MEMFN_DECL(auto query)(this const system_scheduler&, stdexec::get_forward_progress_guarantee_t) noexcept
-> stdexec::forward_progress_guarantee {
return stdexec::forward_progress_guarantee::parallel;
}
STDEXEC_MEMFN_DECL(auto query)(this const system_scheduler&, stdexec::get_domain_t) noexcept
-> system_scheduler_domain {
return {};
}
friend auto operator<=>(const system_scheduler&, const system_scheduler&) = default;
};
struct system_context : stdexec::__immovable {
inline static constexpr std::abi::v1::object_id_t id{/* "execution::system_context::object_t" */};
auto get_scheduler() const noexcept -> system_scheduler {
return {};
}
size_t max_concurrency() const noexcept {
return std::thread::hardware_concurrency();
}
};
template<class Receiver>
struct system_construct_receiver : std::execution::abi::v1::async_object_error_code_receiver::interface_t {
inline static constexpr std::abi::v1::object_id_t id{/* "execution::system_construct_receiver::object_t" */};
Receiver rcvr;
explicit system_construct_receiver(Receiver&& receiver) : rcvr(std::move(receiver)) {}
auto select_interface(std::abi::v1::interface_id_t desired) noexcept
-> std::abi::v1::base::select_interface_result_t override {
auto identity = static_cast<std::execution::abi::v1::async_object_error_code_receiver::interface_t*>(this);
if (std::execution::abi::v1::async_object_error_code_receiver::interface_t::id == desired) {
return {std::abi::v1::base::select_interface_expected_t::implemented, id, desired, identity};
} else if (std::abi::v1::base::interface_t::id == desired) {
return {std::abi::v1::base::select_interface_expected_t::implemented, id, desired, identity};
}
return {std::abi::v1::base::select_interface_expected_t::not_implemented, id, {}, nullptr};
}
auto set_value(std::execution::abi::v1::async_object::interface_t* object) noexcept -> void override {
try {
ex::set_value(std::move(rcvr), async_object{object});
} catch(const std::system_error& ex) {
auto ec = ex.code();
std::print("system_construct_receiver set_value: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
ex::set_error(std::move(rcvr), ec);
}
}
auto set_error(std::error_code ec) noexcept -> void override {
ex::set_error(std::move(rcvr), ec);
}
auto set_stopped() noexcept -> void override {
ex::set_stopped(std::move(rcvr));
}
};
template<class Receiver>
struct system_construct_operation : ex::__immovable {
explicit system_construct_operation(Receiver&& receiver, std::execution::abi::v1::system_context::object_t* storage) : rcvr(std::move(receiver)), stg(storage) {}
system_construct_receiver<Receiver> rcvr;
std::execution::abi::v1::system_context::object_t* stg;
using start_t = ex::start_t;
STDEXEC_MEMFN_DECL(auto start)(this system_construct_operation& __self) noexcept -> void {
try{
auto construct = std::execution::abi::v1::system_context::system_context_construct();
construct->start_construct_operation(__self.stg, &__self.rcvr);
} catch (const std::system_error& ex) {
auto ec = ex.code();
std::print("system_construct_operation start: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
__self.rcvr.set_error(ec);
}
}
};
struct system_context_constructor {
using sender_concept = ex::sender_t;
std::execution::abi::v1::system_context::object_t* storage;
using get_completion_signatures_t = ex::get_completion_signatures_t;
template<class Env>
STDEXEC_MEMFN_DECL(auto get_completion_signatures)(this system_context_constructor, Env&& ) noexcept
-> ex::completion_signatures<
ex::set_value_t(async_object),
ex::set_error_t(std::error_code),
ex::set_stopped_t()> {}
using connect_t = ex::connect_t;
template<class Receiver>
STDEXEC_MEMFN_DECL(auto connect)(this system_context_constructor __self, Receiver&& receiver) noexcept -> system_construct_operation<std::decay_t<Receiver>> {
return system_construct_operation<std::decay_t<Receiver>>{static_cast<Receiver&&>(receiver), __self.storage};
}
};
auto get_system_context() noexcept -> system_context {
return {};
}
auto system_context_construct(std::execution::abi::v1::system_context::object_t* storage) noexcept -> system_context_constructor {
return {storage};
}
} // namespace std::execution::v1
//
// std::v1
//
// this implements an object that has no functionality.
//
namespace std::v1 {
using namespace std::abi::v1;
struct empty_object_t : base::interface_t {
inline static constexpr object_id_t id{/* "empty_object_t" */};
using interface_t = base::interface_t;
auto select_interface(interface_id_t desired) noexcept -> base::select_interface_result_t override {
if (interface_t::id == desired) {
return {base::select_interface_expected_t::implemented, id, interface_t::id, static_cast<interface_t*>(this)};
}
return {base::select_interface_expected_t::not_implemented, id, interface_t::id, nullptr};
}
};
inline static constinit empty_object_t empty_object{};
} // namespace std::v1
namespace stdexec::copy {
/////////////////////////////////////////////////////////////////////////////
// [execution.senders.consumers.run_main_context]
// [execution.senders.consumers.run_main_context_with_variant]
namespace __run_main_context {
inline auto __make_env() noexcept {
return __env::__with(std::execution::v1::get_main_context().get_scheduler(), get_scheduler, get_delegatee_scheduler);
}
struct __env : __result_of<__make_env> {
__env() noexcept
: __result_of<__make_env>{__run_main_context::__make_env()} {
}
};
// What should run_main_context(just_stopped()) return?
template <class _Sender, class _Continuation>
using __run_main_context_result_impl = //
__try_value_types_of_t<
_Sender,
__env,
__transform<__q<__decay_t>, _Continuation>,
__q<__msingle>>;
template <class _Sender>
using __run_main_context_result_t = __mtry_eval<__run_main_context_result_impl, _Sender, __q<std::tuple>>;
template <class _Sender>
using __run_main_context_with_variant_result_t =
__mtry_eval<__run_main_context_result_impl, __result_of<into_variant, _Sender>, __q<__midentity>>;
template <class... _Values>
struct __state {
using _Tuple = std::tuple<_Values...>;
std::variant<std::monostate, _Tuple, std::exception_ptr, set_stopped_t> __data_{};
};
template <class... _Values>
struct __receiver {
struct __t {
using receiver_concept = receiver_t;
using __id = __receiver;
__state<_Values...>* __state_;
std::execution::v1::main_context* __loop_;
template <class _Error>
void __set_error(_Error __err) noexcept {
if constexpr (__decays_to<_Error, std::exception_ptr>)
__state_->__data_.template emplace<2>(static_cast<_Error&&>(__err));
else if constexpr (__decays_to<_Error, std::error_code>)
__state_->__data_.template emplace<2>(std::make_exception_ptr(std::system_error(__err)));
else
__state_->__data_.template emplace<2>(
std::make_exception_ptr(static_cast<_Error&&>(__err)));
}
void __finish() noexcept {
try {
__loop_->finish();
} catch (const std::system_error& ex) {
auto ec = ex.code();
std::print("run_main_context __finish: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
std::terminate();
} catch (const std::exception& ex) {
std::print("run_main_context __finish: {0}: {1}:\n", std::this_thread::get_id(), ex.what());
fflush(stdout);
std::terminate();
} catch (...) {
std::print("run_main_context __finish: {0}:\n", std::this_thread::get_id());
fflush(stdout);
std::terminate();
}
}
template <class... _As>
requires constructible_from<std::tuple<_Values...>, _As...>
STDEXEC_MEMFN_DECL(
void set_value)(this __t&& __rcvr, _As&&... __as) noexcept {
try {
std::print("run_main_context set_value: {0}:\n", std::this_thread::get_id());
fflush(stdout);
__rcvr.__state_->__data_.template emplace<1>(static_cast<_As&&>(__as)...);
} catch (const std::system_error& ex) {
auto ec = ex.code();
std::print("run_main_context set_value: {0}: category {1} message {2}\n", std::this_thread::get_id(), ec.category().name(), ec.message());
fflush(stdout);
__rcvr.__set_error(ec);
} catch (const std::exception& ex) {
std::print("run_main_context set_value: {0}: {1}:\n", std::this_thread::get_id(), ex.what());
fflush(stdout);
__rcvr.__set_error(std::current_exception());
} catch (...) {
std::print("run_main_context set_value: {0}:\n", std::this_thread::get_id());
fflush(stdout);
__rcvr.__set_error(std::current_exception());
}
__rcvr.__finish();
}
template <class _Error>
STDEXEC_MEMFN_DECL(void set_error)(this __t&& __rcvr, _Error __err) noexcept {
__rcvr.__set_error(static_cast<_Error&&>(__err));
__rcvr.__finish();
}
STDEXEC_MEMFN_DECL(void set_stopped)(this __t&& __rcvr) noexcept {
__rcvr.__state_->__data_.template emplace<3>(set_stopped_t{});
__rcvr.__finish();
}
STDEXEC_MEMFN_DECL(auto get_env)(this const __t& __rcvr) noexcept -> __env {
return __env();
}
};
};
template <class _Sender>
using __receiver_t = __t<__run_main_context_result_impl<_Sender, __q<__receiver>>>;
// These are for hiding the metaprogramming in diagnostics
template <class _Sender>
struct __sync_receiver_for {
using __t = __receiver_t<_Sender>;
};
template <class _Sender>
using __sync_receiver_for_t = __t<__sync_receiver_for<_Sender>>;
template <class _Sender>
struct __value_tuple_for {
using __t = __run_main_context_result_t<_Sender>;
};
template <class _Sender>
using __value_tuple_for_t = __t<__value_tuple_for<_Sender>>;
template <class _Sender>
struct __variant_for {
using __t = __run_main_context_with_variant_result_t<_Sender>;
};
template <class _Sender>
using __variant_for_t = __t<__variant_for<_Sender>>;
inline constexpr __mstring __run_main_context_context_diag = //
"In stdexec::run_main_context()..."_mstr;
inline constexpr __mstring __too_many_successful_completions_diag =
"The argument to stdexec::run_main_context() is a sender that can complete successfully in more "
"than one way. Use stdexec::run_main_context_with_variant() instead."_mstr;
template <__mstring _Context, __mstring _Diagnostic>
struct _INVALID_ARGUMENT_TO_RUN_MAIN_CONTEXT_;
template <__mstring _Diagnostic>
using __invalid_argument_to_run_main_context =
_INVALID_ARGUMENT_TO_RUN_MAIN_CONTEXT_<__run_main_context_context_diag, _Diagnostic>;
template <__mstring _Diagnostic, class _Sender, class _Env = __env>
using __run_main_context_error = __mexception<
__invalid_argument_to_run_main_context<_Diagnostic>,
_WITH_SENDER_<_Sender>,
_WITH_ENVIRONMENT_<_Env>>;
template <class _Sender, class>
using __too_many_successful_completions_error =
__run_main_context_error<__too_many_successful_completions_diag, _Sender>;
template <class _Sender>
concept __valid_run_main_context_argument = __ok<__minvoke<
__mtry_catch_q<__single_value_variant_sender_t, __q<__too_many_successful_completions_error>>,
_Sender,
__env>>;
#if STDEXEC_NVHPC()
// It requires some hoop-jumping to get the NVHPC compiler to report a meaningful
// diagnostic for SFINAE failures.
template <class _Sender>
auto __diagnose_error() {
if constexpr (!sender_in<_Sender, __env>) {
using _Completions = __completion_signatures_of_t<_Sender, __env>;
if constexpr (__merror<_Completions>) {
return _Completions();
} else {
constexpr __mstring __diag =
"The stdexec::sender_in<Sender, Environment> concept check has failed."_mstr;
return __run_main_context_error<__diag, _Sender>();
}
} else if constexpr (!__valid_run_main_context_argument<_Sender>) {
return __run_main_context_error<__too_many_successful_completions_diag, _Sender>();
} else if constexpr (!sender_to<_Sender, __sync_receiver_for_t<_Sender>>) {
constexpr __mstring __diag =
"Failed to connect the given sender to run_main_context's internal receiver. "
"The stdexec::connect(Sender, Receiver) expression is ill-formed."_mstr;
return __run_main_context_error<__diag, _Sender>();
} else {
constexpr __mstring __diag = "Unknown concept check failure."_mstr;
return __run_main_context_error<__diag, _Sender>();
}
}
template <class _Sender>
using __error_description_t = decltype(__run_main_context::__diagnose_error<_Sender>());
#endif
////////////////////////////////////////////////////////////////////////////
// [execution.senders.consumers.run_main_context]
struct run_main_context_t {
template <sender_in<__env> _Sender>
requires __valid_run_main_context_argument<_Sender>
&& __has_implementation_for<run_main_context_t, __early_domain_of_t<_Sender>, _Sender>
auto operator()(_Sender&& __sndr) const -> std::optional<__value_tuple_for_t<_Sender>> {
auto __domain = __get_early_domain(__sndr);
return stdexec::apply_sender(__domain, *this, static_cast<_Sender&&>(__sndr));
}
#if STDEXEC_NVHPC()
// This is needed to get sensible diagnostics from nvc++
template <class _Sender, class _Error = __error_description_t<_Sender>>
auto operator()(_Sender&&, [[maybe_unused]] _Error __diagnostic = {}) const
-> std::optional<std::tuple<int>> = delete;
#endif
using _Sender = __0;
using __legacy_customizations_t = __types<
// For legacy reasons:
tag_invoke_t(
run_main_context_t,
get_completion_scheduler_t<set_value_t>(get_env_t(const _Sender&)),
_Sender),
tag_invoke_t(run_main_context_t, _Sender)>;
// The default implementation goes here:
template <class _Sender>
requires sender_to<_Sender, __sync_receiver_for_t<_Sender>>
auto apply_sender(_Sender&& __sndr) const -> std::optional<__run_main_context_result_t<_Sender>> {
using state_t = __run_main_context_result_impl<_Sender, __q<__state>>;
state_t __state{};
std::execution::v1::main_context __loop{std::execution::v1::get_main_context()};
// Launch the sender with a continuation that will fill in a variant
// and notify a condition variable.
auto __op_state =
connect(static_cast<_Sender&&>(__sndr), __receiver_t<_Sender>{&__state, &__loop});
start(__op_state);
// Wait for the variant to be filled in.
__loop.run();
if (__state.__data_.index() == 2)
std::rethrow_exception(std::get<2>(__state.__data_));
if (__state.__data_.index() == 3)
return std::nullopt;
return std::move(std::get<1>(__state.__data_));
}
};
////////////////////////////////////////////////////////////////////////////
// [execution.senders.consumers.run_main_context_with_variant]
struct run_main_context_with_variant_t {
struct __impl;
template <sender_in<__env> _Sender>
requires __callable<
apply_sender_t,
__early_domain_of_t<_Sender>,
run_main_context_with_variant_t,
_Sender>
auto operator()(_Sender&& __sndr) const -> std::optional<__variant_for_t<_Sender>> {
auto __domain = __get_early_domain(__sndr);
return stdexec::apply_sender(__domain, *this, static_cast<_Sender&&>(__sndr));
}
#if STDEXEC_NVHPC()
template <
class _Sender,
class _Error = __error_description_t<__result_of<into_variant, _Sender>>>
auto operator()(_Sender&&, [[maybe_unused]] _Error __diagnostic = {}) const
-> std::optional<std::tuple<std::variant<std::tuple<>>>> = delete;
#endif
using _Sender = __0;
using __legacy_customizations_t = __types<
// For legacy reasons:
tag_invoke_t(
run_main_context_with_variant_t,
get_completion_scheduler_t<set_value_t>(get_env_t(const _Sender&)),
_Sender),
tag_invoke_t(run_main_context_with_variant_t, _Sender)>;
template <class _Sender>
requires __callable<run_main_context_t, __result_of<into_variant, _Sender>>
auto apply_sender(_Sender&& __sndr) const -> std::optional<__variant_for_t<_Sender>> {
if (auto __opt_values = run_main_context_t()(into_variant(static_cast<_Sender&&>(__sndr)))) {
return std::move(std::get<0>(*__opt_values));
}
return std::nullopt;
}
};
} // namespace __run_main_context
using __run_main_context::run_main_context_t;
inline constexpr run_main_context_t run_main_context{};
using __run_main_context::run_main_context_with_variant_t;
inline constexpr run_main_context_with_variant_t run_main_context_with_variant{};
} // namespace stdexec::copy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment