From 3d452cf710b9366bf728ec35d063efb4d6b53a78 Mon Sep 17 00:00:00 2001 From: Karl Wiberg Date: Fri, 11 Sep 2020 16:09:46 +0200 Subject: [PATCH] Proof of concept: Cancer Stick Castle, a sigslot replacement This needs to be followed immediately by a CL that adds unit tests for CancerStickCastle and UntypedFunction. Bug: none Change-Id: I5ade68cc4721d7442db7695f218ecd9be1d639ba Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/182460 Commit-Queue: Karl Wiberg Reviewed-by: Mirko Bonadei Reviewed-by: Lahiru Ginnaliya Gamathige Cr-Commit-Position: refs/heads/master@{#32085} --- rtc_base/BUILD.gn | 17 +++ rtc_base/cancer_stick_castle.cc | 31 +++++ rtc_base/cancer_stick_castle.h | 71 +++++++++++ rtc_base/function.h | 202 ++++++++++++++++++++++++++++++++ rtc_base/system/BUILD.gn | 4 + rtc_base/system/assume.h | 73 ++++++++++++ 6 files changed, 398 insertions(+) create mode 100644 rtc_base/cancer_stick_castle.cc create mode 100644 rtc_base/cancer_stick_castle.h create mode 100644 rtc_base/function.h create mode 100644 rtc_base/system/assume.h diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn index a4dab2d654..1d4a28d3e2 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -43,6 +43,23 @@ rtc_source_set("ignore_wundef") { sources = [ "ignore_wundef.h" ] } +rtc_source_set("function") { + sources = [ "function.h" ] + deps = [ "system:assume" ] +} + +rtc_source_set("cancer_stick_castle") { + sources = [ + "cancer_stick_castle.cc", + "cancer_stick_castle.h", + ] + deps = [ + ":function", + "../api:function_view", + "system:assume", + ] +} + # The subset of rtc_base approved for use outside of libjingle. # TODO(bugs.webrtc.org/9838): Create small and focused build targets and remove # the old concept of rtc_base and rtc_base_approved. diff --git a/rtc_base/cancer_stick_castle.cc b/rtc_base/cancer_stick_castle.cc new file mode 100644 index 0000000000..59288d70d4 --- /dev/null +++ b/rtc_base/cancer_stick_castle.cc @@ -0,0 +1,31 @@ +/* + * Copyright 2020 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "rtc_base/cancer_stick_castle.h" + +namespace webrtc { +namespace cancer_stick_castle_impl { + +CancerStickCastleReceivers::CancerStickCastleReceivers() = default; +CancerStickCastleReceivers::~CancerStickCastleReceivers() = default; + +void CancerStickCastleReceivers::AddReceiverImpl(UntypedFunction* f) { + receivers_.push_back(std::move(*f)); +} + +void CancerStickCastleReceivers::Foreach( + rtc::FunctionView fv) { + for (auto& r : receivers_) { + fv(r); + } +} + +} // namespace cancer_stick_castle_impl +} // namespace webrtc diff --git a/rtc_base/cancer_stick_castle.h b/rtc_base/cancer_stick_castle.h new file mode 100644 index 0000000000..a0bdff9175 --- /dev/null +++ b/rtc_base/cancer_stick_castle.h @@ -0,0 +1,71 @@ +/* + * Copyright 2020 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef RTC_BASE_CANCER_STICK_CASTLE_H_ +#define RTC_BASE_CANCER_STICK_CASTLE_H_ + +#include +#include + +#include "api/function_view.h" +#include "rtc_base/function.h" +#include "rtc_base/system/assume.h" + +namespace webrtc { +namespace cancer_stick_castle_impl { + +class CancerStickCastleReceivers { + public: + CancerStickCastleReceivers(); + ~CancerStickCastleReceivers(); + void AddReceiver(UntypedFunction&& f) { + AddReceiverImpl(&f); + // Assume that f was moved from and is now trivially destructible. + // This helps the compiler optimize away the destructor call. + RTC_ASSUME(f.IsTriviallyDestructible()); + } + void Foreach(rtc::FunctionView fv); + + private: + void AddReceiverImpl(UntypedFunction* f); + std::vector receivers_; +}; + +} // namespace cancer_stick_castle_impl + +// A collection of receivers (callable objects) that can be called all at once. +// Optimized for minimal binary size. +// +// TODO(kwiberg): Add support for removing receivers, if necessary. AddReceiver +// would have to return some sort of ID that the caller could save and then pass +// to RemoveReceiver. Alternatively, the callable objects could return one value +// if they wish to stay in the CSC and another value if they wish to be removed. +// It depends on what's convenient for the callers... +template +class CancerStickCastle { + public: + template + void AddReceiver(F&& f) { + receivers_.AddReceiver( + UntypedFunction::Create(std::forward(f))); + } + void Send(ArgT... args) { + receivers_.Foreach([&](UntypedFunction& f) { + f.Call(std::forward(args)...); + }); + } + + private: + cancer_stick_castle_impl::CancerStickCastleReceivers receivers_; +}; + +} // namespace webrtc + +#endif // RTC_BASE_CANCER_STICK_CASTLE_H_ diff --git a/rtc_base/function.h b/rtc_base/function.h new file mode 100644 index 0000000000..67fcd59653 --- /dev/null +++ b/rtc_base/function.h @@ -0,0 +1,202 @@ +/* + * Copyright 2020 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef RTC_BASE_FUNCTION_H_ +#define RTC_BASE_FUNCTION_H_ + +#include +#include +#include + +#include "rtc_base/system/assume.h" + +namespace webrtc { +namespace webrtc_function_impl { + +using FunVoid = void(); + +union VoidUnion { + void* void_ptr; + FunVoid* fun_ptr; + typename std::aligned_storage<16>::type inline_storage; +}; + +template +struct CallHelpers; +template +struct CallHelpers { + using return_type = RetT; + template + static RetT CallVoidPtr(VoidUnion* vu, ArgT... args) { + return (*static_cast(vu->void_ptr))(std::forward(args)...); + } + static RetT CallFunPtr(VoidUnion* vu, ArgT... args) { + return (reinterpret_cast(vu->fun_ptr))( + std::forward(args)...); + } + template + static RetT CallInlineStorage(VoidUnion* vu, ArgT... args) { + return (*reinterpret_cast(&vu->inline_storage))( + std::forward(args)...); + } + static RetT DoCall(FunVoid* f, VoidUnion* vu, ArgT... args) { + return reinterpret_cast(f)( + vu, std::forward(args)...); + } +}; + +} // namespace webrtc_function_impl + +// A class that holds (and owns) any callable. The same function call signature +// must be provided when constructing and calling the object. +// +// The point of not having the call signature as a class template parameter is +// to have one single concrete type for all signatures; this reduces binary +// size. +class UntypedFunction final { + public: + // Create function for lambdas and other callables; it accepts every type of + // argument except those noted in its enable_if call. + template < + typename Signature, + typename F, + typename std::enable_if< + // Not for function pointers; we have another overload for that below. + !std::is_function::type>::type>::value && + + // Not for nullptr; we have another overload for that below. + !std::is_same::type>::value && + + // Not for UntypedFunction objects; we have another overload for that. + !std::is_same::type>::type>::value>::type* = nullptr> + static UntypedFunction Create(F&& f) { + using F_deref = typename std::remove_reference::type; + // TODO(C++17): Use `constexpr if` here. The compiler appears to do the + // right thing anyway w.r.t. resolving the branch statically and + // eliminating dead code, but it would be good for readability. + if (std::is_trivially_move_constructible::value && + std::is_trivially_destructible::value && + sizeof(F_deref) <= + sizeof(webrtc_function_impl::VoidUnion::inline_storage)) { + // The callable is trivial and small enough, so we just store its bytes + // in the inline storage. + webrtc_function_impl::VoidUnion vu; + new (&vu.inline_storage) F_deref(std::forward(f)); + return UntypedFunction( + vu, + reinterpret_cast( + webrtc_function_impl::CallHelpers< + Signature>::template CallInlineStorage), + nullptr); + } else { + // The callable is either nontrivial or too large, so we can't keep it + // in the inline storage; use the heap instead. + return UntypedFunction( + webrtc_function_impl::VoidUnion{.void_ptr = + new F_deref(std::forward(f))}, + reinterpret_cast( + webrtc_function_impl::CallHelpers< + Signature>::template CallVoidPtr), + static_cast( + [](webrtc_function_impl::VoidUnion* vu) { + // Assuming that this pointer isn't null allows the + // compiler to eliminate a null check in the (inlined) + // delete operation. + RTC_ASSUME(vu->void_ptr != nullptr); + delete reinterpret_cast(vu->void_ptr); + })); + } + } + + // Create function that accepts function pointers. If the argument is null, + // the result is an empty UntypedFunction. + template + static UntypedFunction Create(Signature* f) { + return UntypedFunction( + reinterpret_cast(f), + f ? reinterpret_cast( + webrtc_function_impl::CallHelpers::CallFunPtr) + : nullptr, + nullptr); + } + + // Default constructor. Creates an empty UntypedFunction. + UntypedFunction() : call_(nullptr), delete_(nullptr) {} + + // Not copyable. + UntypedFunction(const UntypedFunction&) = delete; + UntypedFunction& operator=(const UntypedFunction&) = delete; + + // Move construction and assignment. + UntypedFunction(UntypedFunction&& other) + : f_(other.f_), call_(other.call_), delete_(other.delete_) { + other.delete_ = nullptr; + } + UntypedFunction& operator=(UntypedFunction&& other) { + f_ = other.f_; + call_ = other.call_; + delete_ = other.delete_; + other.delete_ = nullptr; + return *this; + } + + ~UntypedFunction() { + if (delete_) { + delete_(&f_); + } + } + + friend void swap(UntypedFunction& a, UntypedFunction& b) { + using std::swap; + swap(a.f_, b.f_); + swap(a.call_, b.call_); + swap(a.delete_, b.delete_); + } + + // Returns true if we have a function, false if we don't (i.e., we're null). + explicit operator bool() const { return call_ != nullptr; } + + template + typename webrtc_function_impl::CallHelpers::return_type Call( + ArgT... args) { + return webrtc_function_impl::CallHelpers::DoCall( + call_, &f_, std::forward(args)...); + } + + // Returns true iff we don't need to call a destructor. This is guaranteed + // to hold for a moved-from object. + bool IsTriviallyDestructible() { return delete_ == nullptr; } + + private: + UntypedFunction(webrtc_function_impl::VoidUnion f, + webrtc_function_impl::FunVoid* call, + void (*del)(webrtc_function_impl::VoidUnion*)) + : f_(f), call_(call), delete_(del) {} + + // The callable thing, or a pointer to it. + webrtc_function_impl::VoidUnion f_; + + // Pointer to a dispatch function that knows the type of the callable thing + // that's stored in f_, and how to call it. An UntypedFunction object is empty + // (null) iff call_ is null. + webrtc_function_impl::FunVoid* call_; + + // Pointer to a function that knows how to delete the callable thing that's + // stored in f_. Null if `f_` is trivially deletable. + void (*delete_)(webrtc_function_impl::VoidUnion*); +}; + +} // namespace webrtc + +#endif // RTC_BASE_FUNCTION_H_ diff --git a/rtc_base/system/BUILD.gn b/rtc_base/system/BUILD.gn index fdb3f96e00..bf8cf94e3a 100644 --- a/rtc_base/system/BUILD.gn +++ b/rtc_base/system/BUILD.gn @@ -44,6 +44,10 @@ rtc_source_set("unused") { sources = [ "unused.h" ] } +rtc_source_set("assume") { + sources = [ "assume.h" ] +} + rtc_source_set("rtc_export") { sources = [ "rtc_export.h", diff --git a/rtc_base/system/assume.h b/rtc_base/system/assume.h new file mode 100644 index 0000000000..231c9e18ad --- /dev/null +++ b/rtc_base/system/assume.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef RTC_BASE_SYSTEM_ASSUME_H_ +#define RTC_BASE_SYSTEM_ASSUME_H_ + +// Possibly evaluate `p`, promising the compiler that the result is true; the +// compiler is allowed (but not required) to use this information when +// optimizing the code. USE WITH CAUTION! If you promise the compiler things +// that aren't true, it will build a broken binary for you. +// +// As a simple example, the compiler is allowed to transform this +// +// RTC_ASSUME(x == 4); +// return x; +// +// into this +// +// return 4; +// +// It is even allowed to propagate the assumption "backwards in time", if it can +// prove that it must have held at some earlier time. For example, the compiler +// is allowed to transform this +// +// int Add(int x, int y) { +// if (x == 17) +// y += 1; +// RTC_ASSUME(x != 17); +// return x + y; +// } +// +// into this +// +// int Add(int x, int y) { +// return x + y; +// } +// +// since if `x` isn't 17 on the third line of the function body, the test of `x +// == 17` on the first line must fail since nothing can modify the local +// variable `x` in between. +// +// The intended use is to allow the compiler to optimize better. For example, +// here we allow the compiler to omit an instruction that ensures correct +// rounding of negative arguments: +// +// int DivBy2(int x) { +// RTC_ASSUME(x >= 0); +// return x / 2; +// } +// +// and here we allow the compiler to possibly omit a null check: +// +// void Delete(int* p) { +// RTC_ASSUME(p != nullptr); +// delete p; +// } +// +// clang-format off +#if defined(__GNUC__) +#define RTC_ASSUME(p) do { if (!(p)) __builtin_unreachable(); } while (0) +#else +#define RTC_ASSUME(p) do {} while (0) +#endif +// clang-format on + +#endif // RTC_BASE_SYSTEM_ASSUME_H_