├── Switch ├── thread.h ├── switch_compiler_aux.h ├── portability.h ├── README.md ├── switch_numops.h ├── switch_algorithms.h ├── date.h ├── switch_dictionary.h ├── switch_atomicops.h ├── ansifmt.h ├── fs.h ├── switch_vector.h ├── switch_exceptions.h ├── text.h ├── switch.h ├── switch_refcnt.h ├── switch_ll.h ├── switch_print.h ├── compress.h ├── timings.h ├── switch_bitops.h ├── switch_ranges.h ├── network.h ├── switch_mallocators.h ├── buffer.h └── switch_common.h ├── Makefile ├── LICENSE ├── README.md ├── coroutines.h └── coroutines.cpp /Switch/thread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Switch 5 | { 6 | using mutex = std::mutex; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /Switch/switch_compiler_aux.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define containerof(type, member_name, ptr) (type *)((char *)(ptr)-offsetof(type, member_name)) 4 | -------------------------------------------------------------------------------- /Switch/portability.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef __linux__ 5 | #define SWITCH_HAVE_MALLOC_USABLE_SIZE 1 6 | #else 7 | #define off64_t off_t 8 | #define pread64 pread 9 | #define pwrite64 pwrite 10 | #define dirent64 dirent 11 | #define readdir64_r readdir_r 12 | #define readdir64 readdir 13 | #endif 14 | -------------------------------------------------------------------------------- /Switch/README.md: -------------------------------------------------------------------------------- 1 | This is a very leak distribution of Switch; it includes the absolute minimum code for this project. 2 | Eventually, we plan to OSS all Switch. Switch is similar in scope and goals to Facebook's Folly library. 3 | 4 | Most of the methods and datastructures here are either wrappers for STL equivalent classes/functions, or are very trimmed down constructs written so that this project can be compiled with them. 5 | -------------------------------------------------------------------------------- /Switch/switch_numops.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | template 5 | [[gnu::always_inline]] inline T Clamp(const T v, const T min, const T max) 6 | { 7 | static_assert(std::is_scalar::value, "Expected scalar"); 8 | return std::min(std::max(v, min), max); 9 | } 10 | 11 | inline static int RoundToMultiple(const int v, const int alignment) 12 | { 13 | const int mask = alignment - 1; 14 | 15 | return (v + mask) & ~mask; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Switch/switch_algorithms.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace SwitchAlgorithms 5 | { 6 | static inline uint64_t Uniform(const uint64_t low, const uint64_t high) 7 | { 8 | return low + (rand()%(high - low)); 9 | } 10 | 11 | inline uint32_t ComputeExponentialBackoffWithDeccorelatedJitter(const uint64_t cap, const uint64_t base, const uint64_t prevSleep) 12 | { 13 | return std::min(cap, Uniform(base, prevSleep * 3)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS:=-std=c++14 -Wstrict-aliasing=2 -Wsequence-point -Warray-bounds -Wextra -Winit-self -Wformat=2 -Wno-format-nonliteral -Wformat-security \ 2 | -Wunused-variable -Wunused-value -Wreturn-type -Wparentheses -Wmissing-braces -Wno-invalid-source-encoding -Wno-invalid-offsetof \ 3 | -Wno-unknown-pragmas -Wno-missing-field-initializers -Wno-unused-parameter -Wno-sign-compare -Wno-invalid-offsetof \ 4 | -fno-rtti -std=c++14 -ffast-math -D_REENTRANT -DREENTRANT -g3 -ggdb -fno-omit-frame-pointer \ 5 | -fno-strict-aliasing -DLEAN_SWITCH -ISwitch/ -Wno-maybe-uninitialized -Wno-unused-function -Wno-uninitialized -funroll-loops -O3 6 | all: client 7 | 8 | client: coroutines.o 9 | ar rcs libcoroutines.a coroutines.o 10 | 11 | .o: .cpp 12 | 13 | clean: 14 | rm -f *.o *.a 15 | 16 | .PHONY: clean 17 | -------------------------------------------------------------------------------- /Switch/date.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Date 4 | { 5 | struct ts_repr 6 | { 7 | const time_t t; 8 | 9 | ts_repr(const time_t v) 10 | : t{v} 11 | { 12 | } 13 | 14 | strwlen8_t Get(char *const out) const 15 | { 16 | struct tm tm; 17 | 18 | localtime_r(&t, &tm); 19 | return strwlen8_t(out, sprintf(out, "%02u.%02u.%02u %02u:%02u:%02u", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec)); 20 | } 21 | }; 22 | } 23 | 24 | static inline void PrintImpl(Buffer &out, const Date::ts_repr &r) 25 | { 26 | struct tm tm; 27 | 28 | localtime_r(&r.t, &tm); 29 | out.AppendFmt("%02u.%02u.%02u %02u:%02u:%02u", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mark Papadakis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Switch/switch_dictionary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Switch 5 | { 6 | template 7 | class unordered_map 8 | : public std::unordered_map 9 | { 10 | public: 11 | struct kv 12 | { 13 | T v; 14 | 15 | const T &value() const 16 | { 17 | return v; 18 | } 19 | }; 20 | 21 | public: 22 | bool Add(const Key &k, const T &v) 23 | { 24 | return this->insert({k, v}).second; 25 | } 26 | 27 | bool Remove(const Key &k) 28 | { 29 | return this->erase(k); 30 | } 31 | 32 | kv detach(const Key &k) 33 | { 34 | auto it = this->find(k); 35 | 36 | if (it != this->end()) 37 | { 38 | auto v = std::move(it->second); 39 | 40 | this->erase(it); 41 | return {v}; 42 | } 43 | else 44 | return {}; 45 | } 46 | }; 47 | } 48 | 49 | namespace std 50 | { 51 | template<> 52 | struct hash 53 | { 54 | using argument_type = void*; 55 | using result_type = std::size_t; 56 | 57 | std::size_t operator()(const void *const ptr) const 58 | { 59 | return std::hash{}(uintptr_t(ptr)); 60 | } 61 | }; 62 | } 63 | 64 | 65 | -------------------------------------------------------------------------------- /Switch/switch_atomicops.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct MemoryModel 5 | { 6 | enum 7 | { 8 | // No barriers or synchronization. 9 | RELAXED = __ATOMIC_RELAXED, 10 | // Data dependency only for both barrier and synchronization with another thread. 11 | CONSUME = __ATOMIC_CONSUME, 12 | // Barrier to hoisting of code and synchronizes with release (or stronger) semantic stores from another thread. 13 | ACQUIRE = __ATOMIC_ACQUIRE, 14 | // Barrier to sinking of code and synchronizes with acquire (or stronger) semantic loads from another thread. 15 | RELEASE = __ATOMIC_RELEASE, 16 | // Full barrier in both directions and synchronizes with acquire loads and release stores in another thread. 17 | ACQ_REL = __ATOMIC_ACQ_REL, 18 | // Full barrier in both directions and synchronizes with acquire loads and release stores in all threads. 19 | SEQ_CST = __ATOMIC_SEQ_CST, 20 | 21 | // moved out of Barriers NS 22 | order_relaxed, 23 | order_acquire, 24 | order_release, 25 | order_acq_rel, 26 | order_seq_cst, 27 | 28 | // memory_order_sync: Forces a full sync: 29 | // #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad 30 | order_sync = order_seq_cst 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /Switch/ansifmt.h: -------------------------------------------------------------------------------- 1 | // http://en.wikipedia.org/wiki/ANSI_escape_code 2 | #pragma once 3 | 4 | namespace ansifmt 5 | { 6 | // http://en.wikipedia.org/wiki/ANSI_escape_code 7 | static constexpr const char *bold = "\033[1m"; 8 | static constexpr const char *reset = "\033[0m"; 9 | static constexpr const char *inverse = "\033[3m"; 10 | 11 | static constexpr const char *color_black = "\033[30m"; 12 | static constexpr const char *color_red = "\033[31m"; 13 | static constexpr const char *color_green = "\033[32m"; 14 | static constexpr const char *color_brown = "\033[33m"; 15 | static constexpr const char *color_blue = "\033[34m"; 16 | static constexpr const char *color_magenta = "\033[35m"; 17 | static constexpr const char *color_cyan = "\033[36m"; 18 | static constexpr const char *color_gray = "\033[37m"; 19 | 20 | 21 | static constexpr const char *bgcolor_black = "\033[40m"; 22 | static constexpr const char *bgcolor_red = "\033[41m"; 23 | static constexpr const char *bgcolor_green = "\033[42m"; 24 | static constexpr const char *bgcolor_brown = "\033[43m"; 25 | static constexpr const char *bgcolor_blue = "\033[44m"; 26 | static constexpr const char *bgcolor_magenta = "\033[45m"; 27 | static constexpr const char *bgcolor_cyan = "\033[46m"; 28 | static constexpr const char *bgcolor_gray = "\033[47m"; 29 | static constexpr const char *cls = "\033[2J\033[1;1H"; 30 | 31 | // http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html 32 | struct set_col 33 | { 34 | const uint32_t column; 35 | 36 | set_col(const uint32_t v) 37 | : column{v} 38 | { 39 | 40 | } 41 | }; 42 | }; 43 | 44 | static void PrintImpl(Buffer &out, const ansifmt::set_col &c) 45 | { 46 | // We need to reset to 0 first with \r and then advance 47 | // maybe there's another escape sequence for explicitly setting the column 48 | out.AppendFmt("\r\033\[<%uC", c.column); 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coros-fibers 2 | Coroutines/Fibers implementation for x86-64 3 | See [Coroutines and Fibers. why and When](https://medium.com/software-development-2/coroutines-and-fibers-why-and-when-5798f08464fd#.qz59082w0) article for information. 4 | 5 | ## Performance 6 | For x86-64, 15 instructions are needed(see `coro_transfer` implementation) for a context switch. 7 | We could do better by disregarding the ABI and tracking fewer registers (and making sure we know what we are doing and the caveats), although I believe its unnecessary, all things considered. We could also improve the scheduler (maybe disable support for priorities and time tracking -- see macros). 8 | On a Xeon 2Ghz system, this implementation will execute 21million context switches/second when using the fibers framework. That's fast enough. 9 | For just context switching (use of `coro_transfer` and `coro_restore`), on the same system, we get 58 to 62 million context switches. This is very fast. 10 | 11 | 12 | ## API 13 | There are two APIs. The lower-level coroutines API (just `coro_transfer` and `coro_restore` for context switching, and two `coro_init` variants, along with 2 more functions for coro stacks ) and the higher level Fibers scheduler API. Please see coroutines.h 14 | 15 | ## Purprose and Benefits 16 | Appropriate use of coroutines/fibers and context switching when needed(for example, when the current task is about to block, or has been monopolizing the CPU for too long) can have a great impact in the performance of your system, because it can help implement fair scheduling and improve the utilization of your OS thread. 17 | They are also extremely easy to use. You can just schedule new coros, yield and await for them with a single function call, no need for continuations or FSMs, or other means of tracking state. Every coroutine has its own stack, and everything just works as expected. 18 | Please see the article for more. 19 | -------------------------------------------------------------------------------- /Switch/fs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "switch.h" 3 | #include 4 | 5 | class DirectoryEntries 6 | { 7 | private: 8 | DIR *const dh; 9 | 10 | public: 11 | struct iterator 12 | { 13 | DIR *const dh; 14 | struct dirent64 *de, storage; 15 | 16 | iterator(DIR *const h, struct dirent64 *const d) 17 | : dh(h), de(d) 18 | { 19 | } 20 | 21 | inline bool operator!=(const struct iterator &o) const 22 | { 23 | return de != o.de; 24 | } 25 | 26 | inline strwlen8_t operator*() const 27 | { 28 | return strwlen8_t(de->d_name, strlen(de->d_name)); 29 | } 30 | 31 | inline iterator &operator++() 32 | { 33 | if (unlikely(readdir64_r(dh, &storage, &de))) 34 | de = nullptr; 35 | return *this; 36 | } 37 | }; 38 | 39 | public: 40 | DirectoryEntries(const char *const path) 41 | : dh(opendir(path)) 42 | { 43 | if (unlikely(!dh)) 44 | throw Switch::exception("Failed to access directory ", path,":", strerror(errno)); 45 | } 46 | 47 | ~DirectoryEntries() 48 | { 49 | if (dh) 50 | closedir(dh); 51 | } 52 | 53 | struct iterator begin() const 54 | { 55 | return iterator(dh, dh ? readdir64(dh) : nullptr); 56 | } 57 | 58 | struct iterator end() const 59 | { 60 | return iterator(dh, nullptr); 61 | } 62 | 63 | operator bool() const 64 | { 65 | return dh; 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /Switch/switch_vector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Switch 5 | { 6 | template 7 | class vector 8 | : public std::vector 9 | { 10 | using Super = std::vector; 11 | 12 | public: 13 | void RemoveByValue(const T v) 14 | { 15 | const auto e = Super::end(); 16 | auto it = std::find(Super::begin(), e, v); 17 | 18 | if (it != e) 19 | Super::erase(it, it + 1); 20 | } 21 | 22 | T Pop() 23 | { 24 | auto last{this->back()}; 25 | 26 | this->pop_back(); 27 | return last; 28 | } 29 | 30 | auto values() 31 | { 32 | return Super::data(); 33 | } 34 | 35 | typename std::vector::iterator begin() 36 | { 37 | return Super::begin(); 38 | } 39 | 40 | typename std::vector::iterator end() 41 | { 42 | return Super::end(); 43 | } 44 | 45 | typename std::vector::const_iterator begin() const 46 | { 47 | return Super::begin(); 48 | } 49 | 50 | typename std::vector::const_iterator end() const 51 | { 52 | return Super::end(); 53 | } 54 | 55 | void pop_front() 56 | { 57 | auto it = Super::begin(); 58 | 59 | Super::erase(it, it + 1); 60 | } 61 | 62 | void Append(T *const list, const size_t n) 63 | { 64 | Super::reserve(n); 65 | for (size_t i{0}; i != n; ++i) 66 | Super::push_back(list[i]); 67 | } 68 | 69 | void PopByIndex(const size_t idx) 70 | { 71 | auto it = Super::begin() + idx; 72 | 73 | Super::erase(it, it + 1); 74 | } 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /Switch/switch_exceptions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "switch_print.h" 3 | 4 | namespace Switch 5 | { 6 | struct exception 7 | : public std::exception 8 | { 9 | Buffer b; 10 | 11 | [[gnu::noinline]] explicit exception(const strwithlen32_t &s) 12 | { 13 | b.Append(s.p, s.len); 14 | } 15 | 16 | template 17 | [[gnu::noinline]] exception(const T &... args) 18 | { 19 | PrintImpl(b, args...); 20 | } 21 | 22 | exception(const exception &o) 23 | : b(o.b) 24 | { 25 | } 26 | 27 | exception(exception &&o) 28 | : b(std::move(o.b)) 29 | { 30 | 31 | } 32 | 33 | exception() = delete; 34 | 35 | const char *what() const noexcept override 36 | { 37 | return b.data(); 38 | } 39 | }; 40 | 41 | struct recoverable_error 42 | : public std::exception 43 | { 44 | Buffer b; 45 | 46 | [[gnu::noinline]] explicit recoverable_error(const strwithlen32_t &s) 47 | { 48 | b.Append(s.p, s.len); 49 | } 50 | 51 | template 52 | [[gnu::noinline]] recoverable_error(const T &... args) 53 | { 54 | PrintImpl(b, args...); 55 | } 56 | 57 | recoverable_error(const recoverable_error &o) 58 | : b(o.b) 59 | { 60 | 61 | } 62 | 63 | recoverable_error(recoverable_error &&o) 64 | : b(std::move(o.b)) 65 | { 66 | 67 | } 68 | 69 | recoverable_error() = delete; 70 | 71 | const char *what() const noexcept override 72 | { 73 | return b.data(); 74 | } 75 | }; 76 | 77 | using runtime_error = recoverable_error; 78 | using range_error = recoverable_error; 79 | using overflow_error = recoverable_error; 80 | using underflow_error = recoverable_error; 81 | using system_error = recoverable_error; 82 | using invalid_argument = recoverable_error; 83 | using length_error = recoverable_error; 84 | using out_of_range = recoverable_error; 85 | using data_error = recoverable_error; 86 | } 87 | 88 | #define SLog(...) ::Print(srcline_repr(), __VA_ARGS__) 89 | -------------------------------------------------------------------------------- /Switch/text.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct size_repr 4 | { 5 | const uint64_t v; 6 | 7 | size_repr(const uint64_t value) 8 | : v{value} 9 | { 10 | } 11 | 12 | strwlen8_t Get(char *const p) const 13 | { 14 | static const std::pair bases[] = 15 | { 16 | {1024ULL * 1024ULL * 1024ULL * 1024ULL, "tb"}, 17 | {1024UL * 1024UL * 1024UL, "gb"}, 18 | {1024 * 1024, "mb"}, 19 | {1024, "kb"}, 20 | }; 21 | 22 | for (const auto &it : bases) 23 | { 24 | const auto r = double(v) / it.first; 25 | 26 | if (r >= 1) 27 | { 28 | const auto repr = it.second; 29 | uint8_t len = sprintf(p, "%.2lf", r); 30 | 31 | while (len && p[len - 1] == '0') 32 | --len; 33 | if (len && p[len - 1] == '.') 34 | --len; 35 | 36 | p[len] = *repr; 37 | p[len + 1] = repr[1]; 38 | return strwlen8_t(p, len + 2); 39 | } 40 | } 41 | 42 | return strwlen8_t(p, sprintf(p, "%ub", uint32_t(v))); 43 | } 44 | }; 45 | 46 | static inline void PrintImpl(Buffer &out, const size_repr &s) 47 | { 48 | out.EnsureCapacity(32); 49 | 50 | out.AdvanceLength(s.Get(out.end()).len); 51 | } 52 | 53 | struct dotnotation_repr 54 | { 55 | const uint64_t value; 56 | const char sep; 57 | 58 | dotnotation_repr(const uint64_t v, const char separator = ',') 59 | : value{v}, sep{separator} 60 | { 61 | } 62 | 63 | strwlen8_t Get(char *const out) const 64 | { 65 | uint16_t t[8]; 66 | uint8_t n{0}; 67 | auto r = value; 68 | char *o; 69 | 70 | do 71 | { 72 | 73 | t[n++] = r % 1000; 74 | r /= 1000; 75 | } while (r); 76 | 77 | for (o = out + sprintf(out, "%u", t[--n]); n;) 78 | { 79 | *o++ = sep; 80 | o += sprintf(o, "%03u", t[--n]); 81 | } 82 | 83 | return {out, uint8_t(o - out)}; 84 | } 85 | }; 86 | 87 | static inline void PrintImpl(Buffer &out, const dotnotation_repr &r) 88 | { 89 | out.EnsureCapacity(32); 90 | out.AdvanceLength(r.Get(out.End()).len); 91 | } 92 | 93 | -------------------------------------------------------------------------------- /Switch/switch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "portability.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define require(x) assert(x) 15 | #define Drequire(x) assert(x) 16 | #define expect(x) assert(x) 17 | #define Dexpect(x) assert(x) 18 | 19 | #if __GNUC__ >= 3 20 | #define likely(x) __builtin_expect(!!(x), 1) 21 | #define unlikely(x) __builtin_expect(!!(x), 0) 22 | #else 23 | #define likely(x) (x) 24 | #define unlikely(x) (x) 25 | #endif 26 | 27 | [[gnu::noreturn]] static inline void Unreachable() 28 | { 29 | __builtin_unreachable(); 30 | } 31 | 32 | 33 | static inline size_t goodMallocSize(const size_t n) noexcept 34 | { 35 | return n; 36 | } 37 | 38 | // Ref: http://cnicholson.net/2011/01/stupid-c-tricks-a-better-sizeof_array/ 39 | // our sizeof_array macro doesn no parameter type-checking; you can pass anything to it, and 40 | // as long as x[0] can be evaluated, you 'll get a successful compilation. 41 | namespace detail 42 | { 43 | template 44 | char (&SIZEOF_ARRAY_REQUIRES_ARRAY_ARGUMENT(T (&)[N]))[N]; // XXX: why does this work and what does it do? 45 | } 46 | #define sizeof_array(x) sizeof(detail::SIZEOF_ARRAY_REQUIRES_ARRAY_ARGUMENT(x)) 47 | 48 | 49 | 50 | 51 | #define STRLEN(p) (uint32_t)(sizeof(p) - 1) 52 | #define STRWITHLEN(p) (p), (uint32_t)(sizeof(p) - 1) 53 | #define LENWITHSTR(p) (uint32_t)(sizeof(p) - 1), (p) 54 | #define STRWLEN(p) STRWITHLEN(p) 55 | #define LENWSTR(p) LENWITHSTR(p) 56 | #define _S(p) STRWITHLEN(p) 57 | 58 | 59 | 60 | #include "switch_common.h" 61 | #include "switch_ranges.h" 62 | #include "buffer.h" 63 | #include "switch_exceptions.h" 64 | #include "switch_numops.h" 65 | #include "switch_ranges.h" 66 | #include "timings.h" 67 | 68 | // Src: folly 69 | template 70 | class AtScopeExit 71 | { 72 | private: 73 | Lambda &l; 74 | 75 | public: 76 | AtScopeExit(Lambda &action) 77 | : l(action) 78 | { 79 | } 80 | 81 | ~AtScopeExit(void) 82 | { 83 | l(); 84 | } 85 | }; 86 | 87 | template 88 | static inline T Min(const T a, const T b) 89 | { 90 | return std::min(a, b); 91 | } 92 | 93 | template 94 | static inline T Max(const T a, const T b) 95 | { 96 | return std::max(a, b); 97 | } 98 | 99 | [[gnu::always_inline]] inline void assume(bool cond) 100 | { 101 | #if defined(__clang__) 102 | __builtin_assume(cond); 103 | #elif defined(__GNUC__) 104 | if (!cond) 105 | __builtin_unreachable(); 106 | #elif defined(_MSC_VER) 107 | __assume(cond); 108 | #endif 109 | } 110 | 111 | [[ noreturn, gnu::always_inline ]] inline void assume_unreachable() 112 | { 113 | assume(false); 114 | #if defined(__GNUC__) 115 | __builtin_unreachable(); 116 | #elif defined(_MSC_VER) 117 | __assume(0); 118 | #else 119 | std::abort(); 120 | #endif 121 | } 122 | 123 | #define TOKEN_PASTE(x, y) x##y 124 | #define TOKEN_PASTE2(x, y) TOKEN_PASTE(x, y) 125 | 126 | #define Auto_INTERNAL1(lname, aname, ...) \ 127 | auto lname = [&]() { \ 128 | __VA_ARGS__; \ 129 | }; \ 130 | AtScopeExit aname(lname); 131 | #define Auto_INTERNAL2(ctr, ...) Auto_INTERNAL1(TOKEN_PASTE(Auto_func_, ctr), TOKEN_PASTE(Auto_Instance_, ctr), __VA_ARGS__) 132 | #define Defer(...) Auto_INTERNAL2(__COUNTER__, __VA_ARGS__) 133 | 134 | #define IMPLEMENT_ME() \ 135 | do \ 136 | { \ 137 | Print(ansifmt::bold, ansifmt::color_red, "Implementation Missing", ansifmt::reset, " at ", __FILE__, ":", __LINE__, ": Will Exit\n"); \ 138 | std::abort(); \ 139 | } while (0) 140 | 141 | #define IMPLEMENT_ME_NOEXIT() Print(ansifmt::bold, ansifmt::color_red, "WARNING: Implementation Missing", ansifmt::reset, " at ", __FILE__, ":", __LINE__, "\n") 142 | 143 | 144 | #include "switch_exceptions.h" 145 | -------------------------------------------------------------------------------- /Switch/switch_refcnt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "switch_atomicops.h" 3 | 4 | struct RCObject 5 | { 6 | virtual void Release() = 0; 7 | 8 | virtual void Retain() = 0; 9 | 10 | virtual int32_t RetainCount() const = 0; 11 | 12 | virtual int32_t use_count() const 13 | { 14 | return RetainCount(); 15 | } 16 | 17 | virtual ~RCObject() 18 | { 19 | 20 | } 21 | }; 22 | 23 | 24 | template 25 | struct RefCounted 26 | : public RCObject 27 | { 28 | private: 29 | int32_t rc = 1; 30 | 31 | public: 32 | ~RefCounted() 33 | { 34 | assert(__atomic_load_n(&rc, MemoryModel::RELAXED) == 0); 35 | } 36 | 37 | void SetOneRef() 38 | { 39 | __atomic_store_n(&rc, 1, MemoryModel::RELAXED); 40 | } 41 | 42 | void ResetRefs() 43 | { 44 | // Useful when you allocat on stack - otherwise destructor will abort 45 | rc = 0; 46 | } 47 | 48 | [[gnu::always_inline]] inline int32_t RetainCount() const override 49 | { 50 | return rc; 51 | } 52 | 53 | [[gnu::always_inline]] inline void TryRelease() 54 | { 55 | const auto p = (uintptr_t)this; 56 | 57 | if (likely(p)) 58 | Release(); 59 | } 60 | 61 | [[gnu::always_inline]] inline void Retain() override 62 | { 63 | const auto now = __atomic_add_fetch(&rc, 1, MemoryModel::RELAXED); 64 | 65 | require(now > 1); // can't go from 0 to 1 66 | } 67 | 68 | [[gnu::always_inline]] inline bool ReleaseAndTestNoRefs() 69 | { 70 | return __atomic_sub_fetch(&rc, 1, MemoryModel::RELEASE) == 0; 71 | } 72 | 73 | 74 | [[gnu::always_inline]] inline void Release() override 75 | { 76 | const auto res = __atomic_sub_fetch(&rc, 1, MemoryModel::RELEASE); 77 | 78 | require(res >= 0); 79 | 80 | if (!res) 81 | { 82 | std::atomic_thread_fence(std::memory_order_acquire); 83 | delete static_cast(this); 84 | } 85 | } 86 | 87 | [[gnu::always_inline]] inline void ReleaseNoDealloc() 88 | { 89 | if (!__atomic_sub_fetch(&rc, 1, MemoryModel::RELEASE)) 90 | { 91 | std::atomic_thread_fence(std::memory_order_acquire); 92 | static_cast(this)->~T(); 93 | } 94 | } 95 | }; 96 | 97 | 98 | namespace Switch 99 | { 100 | template 101 | struct shared_refptr 102 | { 103 | T *v; 104 | 105 | shared_refptr(T *p, const bool) 106 | : v{p} 107 | { 108 | 109 | } 110 | 111 | shared_refptr(T *p) 112 | : v{p} 113 | { 114 | if (v) 115 | v->Retain(); 116 | } 117 | 118 | ~shared_refptr() 119 | { 120 | if (v) 121 | v->Release(); 122 | } 123 | 124 | shared_refptr() 125 | : v{nullptr} 126 | { 127 | 128 | } 129 | 130 | shared_refptr(std::nullptr_t) 131 | : v{nullptr} 132 | { 133 | 134 | } 135 | 136 | shared_refptr(const shared_refptr &o) 137 | : v{o.v} 138 | { 139 | if (v) 140 | v->Retain(); 141 | } 142 | 143 | shared_refptr(shared_refptr &&o) 144 | : v{o.v} 145 | { 146 | o.v = nullptr; 147 | } 148 | 149 | auto &operator=(const shared_refptr &o) 150 | { 151 | if (o.v) 152 | o.v->Retain(); 153 | if (v) 154 | v->Release(); 155 | v = o.v; 156 | return *this; 157 | } 158 | 159 | auto &operator=(shared_refptr &&o) 160 | { 161 | if (v != o.v) 162 | { 163 | if (v) 164 | v->Release(); 165 | v = o.v; 166 | o.v = nullptr; 167 | } 168 | return *this; 169 | } 170 | 171 | void reset(T *const o) 172 | { 173 | if (v != o) 174 | { 175 | if (v) 176 | v->Release(); 177 | 178 | v = o; 179 | if (v) 180 | v->Retain(); 181 | } 182 | } 183 | T *release() 184 | { 185 | auto res = v; 186 | 187 | v = nullptr; 188 | return res; 189 | } 190 | 191 | [[gnu::always_inline]] inline T *get() const 192 | { 193 | return v; 194 | } 195 | 196 | [[gnu::always_inline]] inline T &operator *() const 197 | { 198 | return *v; 199 | } 200 | 201 | [[gnu::always_inline]] inline T *operator->() const 202 | { 203 | return v; 204 | } 205 | 206 | uint32_t use_count() const 207 | { 208 | return v ? v->use_count() : 0; 209 | } 210 | 211 | operator bool() const 212 | { 213 | return v != nullptr; 214 | } 215 | 216 | auto unique() const 217 | { 218 | return use_count() == 1; 219 | } 220 | 221 | // handy -- although not in std::unique_ptr<> API 222 | inline operator T *() 223 | { 224 | return v; 225 | } 226 | }; 227 | 228 | template 229 | static inline auto make_sharedref(T *v) 230 | { 231 | return shared_refptr(std::move(v), true); 232 | } 233 | 234 | template 235 | static inline auto make_sharedref_retained(T *v) 236 | { 237 | return shared_refptr(v); 238 | } 239 | }; 240 | 241 | template 242 | static inline void tryRelease(T *const p) 243 | { 244 | if (p) 245 | p->Release(); 246 | } 247 | -------------------------------------------------------------------------------- /Switch/switch_ll.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "switch_compiler_aux.h" 3 | 4 | struct switch_slist 5 | { 6 | struct switch_slist *next; 7 | }; 8 | 9 | #define switch_slist_foreach(PTR, IT) for (I = (PTR)->next; I; I = I->next) 10 | 11 | static inline void switch_slist_append(switch_slist *s, switch_slist *a) 12 | { 13 | a->next = s->next; 14 | s->next = a; 15 | } 16 | 17 | inline void switch_slist_init(switch_slist *l) 18 | { 19 | l->next = l; 20 | } 21 | 22 | static inline switch_slist *switch_slist_removefirst(switch_slist *s) 23 | { 24 | switch_slist *r = s->next; 25 | 26 | s->next = r->next; 27 | return r; 28 | } 29 | 30 | inline bool switch_slist_isempty(const switch_slist *l) 31 | { 32 | return l->next == l; 33 | } 34 | 35 | inline bool switch_slist_any(const switch_slist *const l) 36 | { 37 | return l->next != l; 38 | } 39 | 40 | 41 | 42 | 43 | 44 | 45 | struct switch_dlist 46 | { 47 | struct switch_dlist *prev, *next; 48 | }; 49 | 50 | static inline void switch_dlist_init(switch_dlist *const l) 51 | { 52 | assume(l); 53 | l->next = l; 54 | l->prev = l; 55 | } 56 | 57 | // @a to head of the list @d 58 | // e.g LIST.next = a 59 | // i.e d.push_back(a) 60 | static inline void switch_dlist_insert_after(switch_dlist *const d, switch_dlist *const a) 61 | { 62 | auto *const dn = d->next; 63 | 64 | a->next = dn; 65 | a->prev = d; 66 | dn->prev= a; 67 | d->next = a; 68 | } 69 | 70 | static inline void switch_dlist_del(switch_dlist *const d) 71 | { 72 | auto *const dp = d->prev; 73 | auto *const dn = d->next; 74 | 75 | dn->prev = dp; 76 | dp->next = dn; 77 | } 78 | 79 | // replace A with B 80 | static inline void switch_dlist_replace(switch_dlist *const a, switch_dlist *const b) 81 | { 82 | switch_dlist_insert_after(a, b); 83 | switch_dlist_del(a); 84 | } 85 | 86 | // @a to tail of the list @d 87 | // e.g LIST.prev = a 88 | static inline void switch_dlist_insert_before(switch_dlist *const d, switch_dlist *const a) 89 | { 90 | auto *const dp = d->prev; 91 | 92 | d->prev = a; 93 | a->next = d; 94 | a->prev = dp; 95 | dp->next= a; 96 | } 97 | 98 | 99 | 100 | 101 | // switch_dlist_del() and switch_dlist_init() @d 102 | // If you want to e.g move an item in an list elsewhere (e.g LRU move to head), you need 103 | // to first switch_dlist_del(), then switch_dlist_init() it and THEN switch_dlist_insert_after() it, otherwise, if you only del()it but 104 | // don't initialize it, it will corrupt the list. Use switch_dlist_del_and_reset() always 105 | static inline void switch_dlist_del_and_reset(switch_dlist *const d) 106 | { 107 | auto *const dp = d->prev; 108 | auto *const dn = d->next; 109 | 110 | d->next = d; 111 | d->prev = d; 112 | 113 | dn->prev = dp; 114 | dp->next = dn; 115 | } 116 | 117 | 118 | static inline switch_dlist *switch_dlist_poplast(switch_dlist *const d) 119 | { 120 | auto *const dp = d->prev; 121 | 122 | if (dp == d) 123 | return nullptr; 124 | else 125 | { 126 | switch_dlist_del(dp); 127 | return dp; 128 | } 129 | } 130 | 131 | static inline switch_dlist *switch_dlist_popfirst(switch_dlist *const d) 132 | { 133 | auto *const dp = d->next; 134 | 135 | if (dp == d) 136 | return nullptr; 137 | else 138 | { 139 | switch_dlist_del(dp); 140 | return dp; 141 | } 142 | } 143 | 144 | static inline void switch_dlist_merge(switch_dlist *const d, switch_dlist *const d2) 145 | { 146 | auto *const dp = d->prev; 147 | auto *const d2n= d2->next; 148 | auto *const d2p= d2->prev; 149 | 150 | if (d2n == d2) 151 | return; // Empty list, don't bother 152 | 153 | dp->next = d2n; 154 | d2n->prev= dp; 155 | d->prev = d2p; 156 | d2p->next= d; 157 | } 158 | 159 | static inline uint32_t switch_dlist_size(const switch_dlist *const l) 160 | { 161 | uint32_t n = 0; 162 | 163 | for (auto *it = l->next; it != l; it = it->next) 164 | ++n; 165 | 166 | return n; 167 | } 168 | 169 | template 170 | static inline auto reverseSinglyList(T *h) 171 | { 172 | T *rh{nullptr}; 173 | 174 | while (h) 175 | { 176 | auto t = h; 177 | 178 | h = t->next; 179 | t->next = rh; 180 | rh = t; 181 | } 182 | return rh; 183 | } 184 | 185 | #ifdef __clang__ 186 | #pragma GCC diagnostic push 187 | #pragma GCC diagnostic ignored "-Winvalid-offsetof" 188 | #endif 189 | 190 | 191 | // Iterate from head .. to tail 192 | #define switch_dlist_foreach_fromhead(PTR, IT) for (IT = (PTR)->next; IT != (PTR); IT = IT->next) 193 | // Iterate from tail .. to head 194 | #define switch_dlist_foreach_fromtail(PTR, IT) for (IT = (PTR)->prev; IT != (PTR); IT = IT->prev) 195 | 196 | #define switch_dlist_isempty(PTR) (((PTR)->next == (PTR))) 197 | #define switch_dlist_any(PTR) (((PTR)->next != (PTR))) 198 | // e.g struct container{ ...; struct switch_dlist itemsList; ..}; struct item { ...; struct switch_dlist list; ... } 199 | // struct item *const theItem = switch_list_entry(item, list, container.itemsList.next); 200 | //#define switch_list_entry(T, M, PTR /* switch_dlist ptr */) (CONTAINER_OF(PTR, T, M)) 201 | #define switch_list_entry(T, M, PTR /* switch_dlist ptr */) (containerof(T, M, PTR)) 202 | 203 | #ifdef __clang__ 204 | #pragma GCC diagnostic pop 205 | #endif 206 | -------------------------------------------------------------------------------- /Switch/switch_print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "buffer.h" 3 | #ifndef __linux__ 4 | #include 5 | #endif 6 | 7 | 8 | struct ptr_repr 9 | { 10 | const void *const ptr; 11 | 12 | ptr_repr(const void *const p) 13 | : ptr(p) 14 | { 15 | 16 | } 17 | 18 | inline strwlen8_t Get(char *const out) const 19 | { 20 | return strwlen8_t(out, sprintf(out, "%p", ptr)); 21 | } 22 | 23 | }; 24 | 25 | static inline void PrintImpl(Buffer &out, const ptr_repr &repr) 26 | { 27 | out.AppendFmt("%p", repr.ptr); 28 | } 29 | 30 | static inline void PrintImpl(void) 31 | { 32 | 33 | } 34 | 35 | static inline void PrintImpl(Buffer &out) 36 | { 37 | 38 | } 39 | 40 | static inline void PrintImpl(Buffer &out, const bool &v) 41 | { 42 | if (v) 43 | out.Append(_S("true")); 44 | else 45 | out.Append(_S("false")); 46 | } 47 | 48 | static inline void PrintImpl(Buffer &out, const double &v) 49 | { 50 | out.AppendFmt("%lf", v); 51 | } 52 | 53 | static inline void PrintImpl(Buffer &out, const char v) 54 | { 55 | out.AppendFmt("%c", v); 56 | } 57 | 58 | static inline void PrintImpl(Buffer &out, const void *const ptr) 59 | { 60 | out.AppendFmt("%p", ptr); 61 | } 62 | 63 | static inline void PrintImpl(Buffer &out, void *const ptr) 64 | { 65 | out.AppendFmt("%p", ptr); 66 | } 67 | 68 | static inline void PrintImpl(Buffer &out, const float &v) 69 | { 70 | out.AppendFmt("%f", v); 71 | } 72 | 73 | static inline void PrintImpl(Buffer &out, const int &v) 74 | { 75 | out.AppendFmt("%d", v); 76 | } 77 | 78 | static inline void PrintImpl(Buffer &out, const char *p) 79 | { 80 | if (likely(p)) 81 | out.Append(p, strlen(p)); 82 | else 83 | out.Append(_S("(nullptr)")); 84 | } 85 | 86 | static inline void PrintImpl(Buffer &out, char *p) 87 | { 88 | if (likely(p)) 89 | out.Append(p, strlen(p)); 90 | else 91 | out.Append(_S("(nullptr)")); 92 | 93 | } 94 | 95 | static inline void PrintImpl(Buffer &out, const uint32_t &v) 96 | { 97 | out.AppendFmt("%" PRIu32, v); 98 | } 99 | 100 | static inline void PrintImpl(Buffer &out, const uint8_t &v) 101 | { 102 | out.AppendFmt("%" PRIu32, v); 103 | } 104 | 105 | static inline void PrintImpl(Buffer &out, const uint16_t &v) 106 | { 107 | out.AppendFmt("%" PRIu32, v); 108 | } 109 | 110 | static inline void PrintImpl(Buffer &out, const int16_t &v) 111 | { 112 | out.AppendFmt("%" PRId32, v); 113 | } 114 | 115 | static inline void PrintImpl(Buffer &out, const int8_t &v) 116 | { 117 | out.AppendFmt("%" PRId32, v); 118 | } 119 | 120 | static inline void PrintImpl(Buffer &out, const uint64_t &v) 121 | { 122 | out.AppendFmt("%" PRIu64, v); 123 | } 124 | 125 | static inline void PrintImpl(Buffer &out, const int64_t &v) 126 | { 127 | out.AppendFmt("%" PRId64, v); 128 | } 129 | 130 | static inline void PrintImpl(Buffer &out, const Buffer &o) 131 | { 132 | out.Append(o); 133 | } 134 | 135 | static inline void PrintImpl(Buffer &out, const strwlen8_t &o) 136 | { 137 | out.Append(o); 138 | } 139 | 140 | static inline void PrintImpl(Buffer &out, const strwlen16_t &o) 141 | { 142 | out.Append(o); 143 | } 144 | 145 | static inline void PrintImpl(Buffer &out, const strwlen32_t &o) 146 | { 147 | out.Append(o); 148 | } 149 | 150 | 151 | 152 | 153 | template 154 | static void PrintImpl(Buffer &b, const T &v, const Args&... args) 155 | { 156 | PrintImpl(b, v); 157 | PrintImpl(b, args...); 158 | } 159 | 160 | #ifndef __linux__ 161 | static pthread_key_t bufKey; 162 | 163 | [[gnu::constructor]] static void _init() 164 | { 165 | pthread_key_create(&bufKey, nullptr); 166 | } 167 | 168 | [[gnu::destructor]] static void _tear_down() 169 | { 170 | pthread_key_delete(bufKey); 171 | } 172 | #endif 173 | 174 | static Buffer &thread_local_buf() 175 | { 176 | #ifdef __linux__ 177 | static thread_local Buffer b; 178 | 179 | return b; 180 | #else 181 | auto p = (Buffer *)pthread_getspecific(bufKey); 182 | 183 | if (!p) 184 | { 185 | p = new Buffer(); 186 | pthread_setspecific(bufKey, p); 187 | } 188 | 189 | return *p; 190 | #endif 191 | } 192 | 193 | template 194 | static void Print(const T &v, const Args&... args) 195 | { 196 | auto &b = thread_local_buf(); 197 | 198 | b.clear(); 199 | PrintImpl(b, v); 200 | PrintImpl(b, args...); 201 | const auto r = write(STDOUT_FILENO, b.data(), b.length()); 202 | 203 | (void)r; // (void)write triggers warning if -Wunused-result is set 204 | // and write() is declared like so 205 | } 206 | 207 | 208 | template 209 | static void ToBuffer(Buffer &out, const T &v, const Args&... args) 210 | { 211 | PrintImpl(out, v); 212 | PrintImpl(out, args...); 213 | } 214 | 215 | 216 | template 217 | static void PrintImpl(Buffer &b, const std::pair &pair) 218 | { 219 | b.Append('<'); 220 | PrintImpl(b, pair.first); 221 | b.Append(_S(", ")); 222 | PrintImpl(b, pair.second); 223 | b.Append('>'); 224 | } 225 | 226 | 227 | 228 | template 229 | static void PrintImpl(Buffer &b, const T &v) 230 | { 231 | if (std::is_enum::value) 232 | { 233 | // We can now use 'naked' enums and they will be printed properly 234 | PrintImpl(b, (typename std::underlying_type::type)v); 235 | } 236 | else 237 | { 238 | // catch-all for when we have no PrintImpl() specialization 239 | fprintf(stderr, "Specialization for type not defined\n"); 240 | std::abort(); 241 | } 242 | } 243 | 244 | 245 | // Handy alternative to snprintf() 246 | // See also: 247 | // Buffer::append<> 248 | // Buffer::build<> 249 | // RPCString::build<> 250 | template 251 | static size_t Snprint(char *out, const size_t outLen, Args&&... args) 252 | { 253 | auto &b = thread_local_buf(); 254 | 255 | b.clear(); 256 | ToBuffer(b, std::forward(args)...); 257 | 258 | const auto l = b.length(); 259 | 260 | b.AsS32().ToCString(out, outLen); 261 | return l; 262 | } 263 | -------------------------------------------------------------------------------- /Switch/compress.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "switch.h" 3 | #include "ext_snappy/snappy.h" 4 | 5 | namespace Compression 6 | { 7 | 8 | enum class Algo : int8_t 9 | { 10 | UNKNOWN = -1, 11 | SNAPPY 12 | }; 13 | 14 | inline bool Compress(const Algo algorithm, const void *data, const uint32_t dataLen, Buffer *dest) 15 | { 16 | switch (algorithm) 17 | { 18 | case Algo::SNAPPY: 19 | { 20 | size_t outLen = 0; 21 | 22 | dest->EnsureCapacity(snappy::MaxCompressedLength(dataLen + 2)); 23 | snappy::RawCompress((char *)data, dataLen, dest->At(dest->length()), &outLen); 24 | dest->AdvanceLength(outLen); 25 | 26 | return true; 27 | } 28 | break; 29 | 30 | default: 31 | return false; 32 | } 33 | } 34 | 35 | inline bool UnCompress(const Algo algorithm, const void *const source, const uint32_t sourceLen, Buffer *const dest) 36 | { 37 | switch (algorithm) 38 | { 39 | case Algo::SNAPPY: 40 | { 41 | size_t outLen; 42 | 43 | if (unlikely(!snappy::GetUncompressedLength((char *)source, sourceLen, &outLen))) 44 | return false; 45 | else 46 | { 47 | dest->EnsureCapacity(outLen + 8); 48 | 49 | if (unlikely(!snappy::RawUncompress((char *)source, sourceLen, dest->At(dest->length())))) 50 | return false; 51 | else 52 | { 53 | dest->AdvanceLength(outLen); 54 | return true; 55 | } 56 | } 57 | } 58 | break; 59 | 60 | default: 61 | return false; 62 | } 63 | } 64 | 65 | inline uint8_t *PackUInt32(const uint32_t n, uint8_t *out) 66 | { 67 | #define AS_FLIPPED(_v_) (_v_) | 128 68 | // This would have worked if it wasn't for the edge cases of e.g (1<<7), etc 69 | //#define AS_FLIPPED_F(v) (((v)&127) + 128) 70 | #define AS_FLIPPED_F(_v_) (_v_) | 128 71 | 72 | if (n < (1 << 7)) 73 | { 74 | *(out++) = n; 75 | } 76 | else if (n < (1 << 14)) 77 | { 78 | *(out++) = AS_FLIPPED(n); 79 | *(out++) = n >> 7; 80 | } 81 | else if (n < (1 << 21)) 82 | { 83 | *(out++) = AS_FLIPPED(n); 84 | *(out++) = AS_FLIPPED_F(n >> 7); 85 | *(out++) = n >> 14; 86 | } 87 | else if (n < (1 << 28)) 88 | { 89 | *(out++) = AS_FLIPPED(n); 90 | *(out++) = AS_FLIPPED_F(n >> 7); 91 | *(out++) = AS_FLIPPED_F(n >> 14); 92 | *(out++) = n >> 21; 93 | } 94 | else 95 | { 96 | *(out++) = AS_FLIPPED(n); 97 | *(out++) = AS_FLIPPED_F(n >> 7); 98 | *(out++) = AS_FLIPPED_F(n >> 14); 99 | *(out++) = AS_FLIPPED_F(n >> 21); 100 | *(out++) = n >> 28; 101 | } 102 | 103 | return out; 104 | #undef AS_FLIPPED 105 | #undef AS_FLIPPED_V 106 | } 107 | 108 | inline uint8_t UnpackUInt32Check(const uint8_t *p, const uint8_t *const e) 109 | { 110 | for (uint8_t i{0}; i != 5; ++i) 111 | { 112 | if (unlikely(p >= e)) 113 | break; 114 | else if (*p < 127) 115 | return i + 1; 116 | else 117 | ++p; 118 | } 119 | 120 | return 0; 121 | } 122 | 123 | inline uint32_t UnpackUInt32(const uint8_t *&buf) 124 | { 125 | #define FLIPPED(v) ((v) & ~128) 126 | if (buf[0] > 127) 127 | { 128 | if (buf[1] > 127) 129 | { 130 | if (buf[2] > 127) 131 | { 132 | if (buf[3] > 127) 133 | { 134 | const uint32_t r = FLIPPED(buf[0]) | (FLIPPED(buf[1]) << 7) | (FLIPPED(buf[2]) << 14) | (FLIPPED(buf[3]) << 21) | (buf[4] << 28); 135 | 136 | buf += 5; 137 | return r; 138 | } 139 | else 140 | { 141 | const uint32_t r = FLIPPED(buf[0]) | (FLIPPED(buf[1]) << 7) | (FLIPPED(buf[2]) << 14) | (buf[3] << 21); 142 | 143 | buf += 4; 144 | return r; 145 | } 146 | } 147 | else 148 | { 149 | const uint32_t r = FLIPPED(buf[0]) | (FLIPPED(buf[1]) << 7) | (buf[2] << 14); 150 | 151 | buf += 3; 152 | return r; 153 | } 154 | } 155 | else 156 | { 157 | const uint32_t r = FLIPPED(buf[0]) | (buf[1] << 7); 158 | 159 | buf += 2; 160 | return r; 161 | } 162 | } 163 | else 164 | return *buf++; 165 | #undef FLIPPED 166 | } 167 | } 168 | 169 | void IOBuffer::SerializeVarUInt32(const uint32_t n) 170 | { 171 | EnsureCapacity(8); 172 | 173 | uint8_t *e = (uint8_t *)(buffer + length_); 174 | length_ += Compression::PackUInt32(n, e) - e; 175 | } 176 | 177 | uint32_t IOBuffer::UnserializeVarUInt32(void) 178 | { 179 | const uint8_t *e = (uint8_t *)(buffer + position), *const b = e; 180 | const uint32_t r = Compression::UnpackUInt32(e); 181 | 182 | position += e - b; 183 | return r; 184 | } 185 | -------------------------------------------------------------------------------- /Switch/timings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #ifdef __MACH__ 8 | #include 9 | #include 10 | #endif 11 | 12 | #ifdef __MACH__ 13 | static clock_serv_t cclock; 14 | 15 | [[gnu::constructor]] static void __init() 16 | { 17 | host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); 18 | } 19 | 20 | [[gnu::destructor]] static void __dtor() 21 | { 22 | mach_port_deallocate(mach_task_self(), cclock); 23 | } 24 | #endif 25 | 26 | namespace Timings 27 | { 28 | namespace SystemClock 29 | { 30 | static uint64_t Tick() 31 | { 32 | struct timespec res; 33 | 34 | #ifdef __MACH__ 35 | mach_timespec_t mts; 36 | 37 | clock_get_time(cclock, &mts); 38 | res.tv_sec = mts.tv_sec; 39 | res.tv_nsec = mts.tv_nsec; 40 | #else 41 | clock_gettime(CLOCK_MONOTONIC, &res); 42 | #endif 43 | 44 | return (res.tv_sec * 1000000000ULL) + res.tv_nsec; 45 | } 46 | 47 | static inline uint64_t Time(); 48 | 49 | [[gnu::always_inline]] inline static uint64_t CurTimeInNS() 50 | { 51 | return Tick(); 52 | } 53 | 54 | [[gnu::always_inline]] inline static uint64_t CurTimeInMillis() 55 | { 56 | return Time(); 57 | } 58 | } 59 | 60 | template 61 | struct Unit 62 | { 63 | static inline void Set(const uint64_t n, timespec *const ts) 64 | { 65 | ts->tv_sec = ToSeconds(n); 66 | ts->tv_nsec = (n * asNanoseconds) - (ts->tv_sec * 1000000000ULL); 67 | } 68 | 69 | static inline uint64_t Tick() 70 | { 71 | return SystemClock::Tick() / asNanoseconds; 72 | } 73 | 74 | static inline uint64_t Since(const uint64_t t) 75 | { 76 | return Tick() - t; 77 | } 78 | 79 | static inline uint64_t ToSeconds(const uint64_t n) 80 | { 81 | return n * asNanoseconds / 1000000000ULL; 82 | } 83 | 84 | static inline timespec ToTimespec(const uint64_t n) 85 | { 86 | timespec res; 87 | const auto asN(n * asNanoseconds); 88 | 89 | res.tv_sec = asN / 1000000000ULL; 90 | res.tv_nsec = asN - res.tv_sec * 1000000000ULL; 91 | 92 | return res; 93 | } 94 | 95 | static constexpr inline uint64_t ToMinutes(const uint64_t n) 96 | { 97 | return ToSeconds(n) / 60; 98 | } 99 | 100 | static constexpr inline uint64_t ToHours(const uint64_t n) 101 | { 102 | return ToMinutes(n) / 60; 103 | } 104 | 105 | static constexpr inline uint64_t ToDays(const uint64_t n) 106 | { 107 | return ToHours(n) / 24; 108 | } 109 | 110 | static constexpr inline uint64_t ToMicros(const uint64_t n) 111 | { 112 | return n * asNanoseconds / 1000; 113 | } 114 | 115 | static constexpr inline uint64_t ToMillis(const uint64_t n) 116 | { 117 | return n * asNanoseconds / 1000000; 118 | } 119 | 120 | static constexpr inline uint64_t ToNanos(const uint64_t n) 121 | { 122 | return n * asNanoseconds; 123 | } 124 | 125 | static void Sleep(const uint64_t n) 126 | { 127 | timespec req, rem; 128 | 129 | req.tv_sec = ToSeconds(n); 130 | req.tv_nsec = (n * asNanoseconds) - (req.tv_sec * 1000000000); 131 | 132 | while (unlikely(nanosleep(&req, &rem) == -1 && errno == EINTR)) 133 | req = rem; 134 | } 135 | 136 | // Need all those signatures to avoid ambiguity 137 | static inline void Sleep(const uint32_t n) 138 | { 139 | Sleep((uint64_t)n); 140 | } 141 | 142 | static inline void Sleep(const int32_t n) 143 | { 144 | Sleep((uint64_t)n); 145 | } 146 | 147 | static inline void Sleep(const uint16_t n) 148 | { 149 | Sleep((uint64_t)n); 150 | } 151 | 152 | static inline void Sleep(const uint8_t n) 153 | { 154 | Sleep((uint64_t)n); 155 | } 156 | 157 | static void SleepInterruptible(const uint64_t n) 158 | { 159 | struct timespec req, rem; 160 | 161 | req.tv_sec = ToSeconds(n); 162 | req.tv_nsec = (n * asNanoseconds) - (req.tv_sec * 1000000000); 163 | 164 | nanosleep(&req, &rem); 165 | } 166 | 167 | static uint64_t SysTime() 168 | { 169 | struct timeval tv; 170 | 171 | if (unlikely(gettimeofday(&tv, nullptr) == -1)) 172 | { 173 | abort(); 174 | } 175 | else if (unlikely(tv.tv_sec < 1451982426u)) 176 | { 177 | abort(); 178 | } 179 | else 180 | return ((tv.tv_sec * 1000000000ULL) + (tv.tv_usec * 1000ULL)) / asNanoseconds; 181 | } 182 | }; 183 | 184 | struct Seconds 185 | : public Unit<1000000000ULL> 186 | { 187 | }; 188 | 189 | struct Milliseconds 190 | : public Unit<1000000UL> 191 | { 192 | }; 193 | 194 | struct Microseconds 195 | : public Unit<1000> 196 | { 197 | }; 198 | 199 | struct Nanoseconds 200 | : public Unit<1> 201 | { 202 | }; 203 | 204 | struct Minutes 205 | : public Unit<1000000000ULL * 60> 206 | { 207 | }; 208 | 209 | struct Hours 210 | : public Unit<1000000000ULL * 60 * 60> 211 | { 212 | }; 213 | 214 | struct Days 215 | : public Unit<1000000000ULL * 60 * 60 * 24> 216 | { 217 | }; 218 | 219 | struct Weeks 220 | : public Unit<1000000000ULL * 60 * 60 * 24 * 7> 221 | { 222 | }; 223 | 224 | uint64_t SystemClock::Time() 225 | { 226 | return Timings::Nanoseconds::ToMillis(Tick()); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /Switch/switch_bitops.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace SwitchBitOps 7 | { 8 | // http://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord 9 | // Fast way to test if a zero byte is in a word 10 | [[gnu::always_inline]] inline constexpr bool U32HasZero(const uint32_t v) 11 | { 12 | return (v - 0x01010101UL) & ~v & 0x80808080UL; 13 | } 14 | 15 | // To check for 4 different bytes, xor the word with those bytes and then check for zero bytes: 16 | // v = (((unsigned char)c * 0x1010101U) ^ delimiter) 17 | // where delimter is the 4byte value to look for (u32) 18 | // and c is the character to check 19 | // 20 | // e.g to check if a character is either of (?, /, -, :) 21 | // IsDelimter(uint8_t(c), '?'<<24|'/'<<16, '-'<<8, ':'); 22 | // 23 | // Those are used by HAProxy / pattern.c 24 | [[gnu::always_inline]] inline static bool IsDelimter(const uint8_t c, uint32_t mask) 25 | { 26 | mask ^= (c * 0x01010101); // propagate the char to all 4 bytes 27 | return (mask - 0x01010101) & ~mask & 0x80808080U; 28 | } 29 | 30 | 31 | 32 | 33 | template 34 | [[gnu::always_inline]] inline bool IsSet(const T n, const uint8_t index) 35 | { 36 | return n&(((T)1)< 40 | [[gnu::always_inline]] inline static constexpr T MSB(const T n, const uint8_t span) 41 | { 42 | return n >> ((sizeof(T) * 8) - span); 43 | } 44 | 45 | template 46 | [[gnu::always_inline]] inline static constexpr T LSBMask(const uint8_t span) 47 | { 48 | return ((T)-1) >> ((sizeof(T) * 8) - span); 49 | } 50 | 51 | template 52 | [[gnu::always_inline]] inline static constexpr T LSB(const T n, const uint8_t span) 53 | { 54 | return n & LSBMask(span); 55 | } 56 | 57 | template 58 | [[gnu::always_inline]] inline static constexpr uint8_t LeadingZeros(const T v) 59 | { 60 | return __builtin_clz(v); 61 | } 62 | 63 | // Need specialized for (u16, u8) because __builtin_clz() operates on uints 64 | [[gnu::always_inline]] inline static constexpr uint8_t LeadingZeros(const uint16_t v) 65 | { 66 | return __builtin_clz(v) - 16; 67 | } 68 | 69 | [[gnu::always_inline]] inline static constexpr uint8_t LeadingZeros(const uint8_t v) 70 | { 71 | return __builtin_clz(v) - 24; 72 | } 73 | 74 | [[gnu::always_inline]] inline static constexpr uint8_t LeadingZeros(const uint64_t v) // if v == 0 return std::numeric_limits::digits 75 | { 76 | return __builtin_clzll(v); 77 | } 78 | 79 | template 80 | [[gnu::always_inline]] inline static constexpr uint8_t TrailingZeros(const T v) 81 | { 82 | return __builtin_ctz(v); 83 | } 84 | 85 | [[gnu::always_inline]] inline static constexpr uint8_t TrailingZeros(const uint64_t v) 86 | { 87 | return __builtin_ctzll(v); 88 | } 89 | 90 | [[gnu::always_inline]] inline static uint8_t TrailingZeros(const int64_t v) 91 | { 92 | return __builtin_ctzll(*(uint64_t *)&v); 93 | } 94 | 95 | 96 | #ifdef SWITCH_ARCH_64BIT 97 | [[gnu::always_inline]] inline static constexpr uint8_t TrailingZeros(const long long v) 98 | { 99 | return __builtin_ctzll(v); 100 | } 101 | #endif 102 | 103 | 104 | template 105 | [[gnu::always_inline]] inline static constexpr uint8_t PopCnt(const T v) 106 | { 107 | return __builtin_popcount(v); 108 | } 109 | 110 | [[gnu::always_inline]] inline static constexpr uint8_t PopCnt(const uint64_t v) 111 | { 112 | return __builtin_popcountll(v); 113 | } 114 | 115 | 116 | // Returns 1 + index of the least significant 1-bit of x, or if x == 0, returns 0 117 | template 118 | [[gnu::always_inline]] inline static constexpr uint8_t LeastSignificantBitSet(const T v) 119 | { 120 | return __builtin_ffs(v); 121 | } 122 | 123 | [[gnu::always_inline]] inline static constexpr uint8_t LeastSignificantBitSet(const uint64_t v) 124 | { 125 | return __builtin_ffsll(v); 126 | } 127 | 128 | 129 | 130 | // https://en.wikipedia.org/wiki/Hamming_distance 131 | // The number of positions where the corresponding bits differ. 132 | template 133 | [[gnu::always_inline]] inline constexpr uint8_t HammingDistance(const T h1, const T h2) 134 | { 135 | return PopCnt(h1 ^ h2); 136 | } 137 | 138 | 139 | // A lean bitmap 140 | // 141 | // We could get log2(sizeof(T) << 8) and shift by that instead of 142 | // dividing but the compiler should be smart enough to figure that out anyway 143 | template 144 | struct Bitmap 145 | { 146 | static_assert(std::numeric_limits::is_integer, "T must be an integer"); 147 | static_assert(!std::numeric_limits::is_signed, "T must be an unsigned integer"); 148 | 149 | private: 150 | T *const bitmap; 151 | 152 | public: 153 | static inline uint32_t FirstSet(const T *const bm, const uint32_t bmSize /* in Ts not in bits */) 154 | { 155 | for (uint32_t i{0}; i != bmSize; ++i) 156 | { 157 | if (const auto v = bm[i]) 158 | return (63 - SwitchBitOps::LeadingZeros(v)) + (i * sizeof(T) << 3); 159 | } 160 | 161 | return UINT32_MAX; 162 | } 163 | 164 | static auto cardinality(const T *const bm, const uint32_t n /* in Ts, not in bits */) 165 | { 166 | size_t cnt{0}; 167 | 168 | for (uint32_t i{0}; i != n; ++i) 169 | { 170 | if (const auto v = bm[i]) 171 | cnt+=PopCnt(v); 172 | } 173 | 174 | return cnt; 175 | } 176 | 177 | static auto anySet(const T *const bm, const uint32_t n /* in Ts, not in bits */) 178 | { 179 | for (uint32_t i{0}; i != n; ++i) 180 | { 181 | if (bm[i]) 182 | return true; 183 | } 184 | return false; 185 | } 186 | 187 | static inline void Set(T *const bm, const uint32_t index) 188 | { 189 | const auto i = index / (sizeof(T)<<3); 190 | const T mask = (T)1U << (index&((sizeof(T) * 8) - 1)); 191 | 192 | bm[i]|=mask; 193 | } 194 | 195 | static inline void Toggle(T *const bm, const uint32_t index) 196 | { 197 | const auto i = index / (sizeof(T)<<3); 198 | const T mask = (T)1U << (index&((sizeof(T) * 8) - 1)); 199 | 200 | bm[i]^=mask; 201 | } 202 | 203 | static inline bool SetIfUnset(T *const bm, const uint32_t index) 204 | { 205 | const auto i = index / (sizeof(T)<<3); 206 | const T mask = (T)1U << (index&((sizeof(T) * 8) - 1)); 207 | auto &v = bm[i]; 208 | 209 | if (v&mask) 210 | return false; 211 | else 212 | { 213 | v|=mask; 214 | return true; 215 | } 216 | } 217 | 218 | static inline void Unset(T *const bm, const uint32_t index) 219 | { 220 | const auto i = index / (sizeof(T)<<3); 221 | const T mask = (T)1U << (index&((sizeof(T) * 8) - 1)); 222 | 223 | bm[i]&=~mask; 224 | } 225 | 226 | static inline bool IsSet(T *const bm, const uint32_t index) 227 | { 228 | const auto i = index / (sizeof(T)<<3); 229 | const T mask = (T)1U << (index&((sizeof(T) * 8) - 1)); 230 | 231 | return bm[i]&mask; 232 | } 233 | 234 | 235 | 236 | public: 237 | Bitmap(const uint32_t capacity) 238 | : bitmap{ (T *)calloc((capacity / (sizeof(T) << 3)) + 1, sizeof(T)) } 239 | { 240 | 241 | } 242 | 243 | ~Bitmap(void) 244 | { 245 | ::free(bitmap); 246 | } 247 | 248 | int MLock(const uint32_t capacity) 249 | { 250 | return mlock(bitmap, ((capacity / (sizeof(T) << 3)) + 1) * sizeof(T)); 251 | } 252 | 253 | int MUnlock(const uint32_t capacity) 254 | { 255 | return munlock(bitmap, ((capacity / (sizeof(T) << 3)) + 1) * sizeof(T)); 256 | } 257 | 258 | inline void Set(const uint32_t index) 259 | { 260 | return Set(bitmap, index); 261 | } 262 | 263 | inline bool SetIfUnset(const uint32_t index) 264 | { 265 | return SetIfUnset(bitmap, index); 266 | } 267 | 268 | inline void Unset(const uint32_t index) 269 | { 270 | return Unset(bitmap, index); 271 | } 272 | 273 | inline bool IsSet(const uint32_t index) const 274 | { 275 | return IsSet(bitmap, index); 276 | } 277 | 278 | T *Data(void) const 279 | { 280 | return bitmap; 281 | } 282 | 283 | bool FromFile(const char *const path) 284 | { 285 | int fd = open(path, O_RDONLY|O_LARGEFILE); 286 | 287 | if (fd == -1) 288 | return false; 289 | 290 | const auto fileSize = lseek64(fd, 0, SEEK_END); 291 | 292 | if (pread64(fd, bitmap, fileSize, 0) != fileSize) 293 | { 294 | (void)close(fd); 295 | return false; 296 | } 297 | else 298 | { 299 | (void)close(fd); 300 | return true; 301 | } 302 | } 303 | 304 | }; 305 | }; 306 | -------------------------------------------------------------------------------- /Switch/switch_ranges.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // range is (Start():inclusive ... End():exclusive) 4 | // i.e [left, right) 5 | template 6 | struct range_base 7 | { 8 | struct iterator 9 | { 10 | VT i; 11 | 12 | iterator(const VT index) 13 | : i{index} 14 | { 15 | 16 | } 17 | 18 | VT inline operator*() const 19 | { 20 | return i; 21 | } 22 | 23 | void operator++() 24 | { 25 | ++i; 26 | } 27 | 28 | bool operator!=(const iterator &o) const 29 | { 30 | return i != o.i; 31 | } 32 | }; 33 | 34 | VT offset; 35 | LT len; 36 | 37 | iterator begin() const 38 | { 39 | return iterator(offset); 40 | } 41 | 42 | iterator end() const 43 | { 44 | return iterator(End()); 45 | } 46 | 47 | static inline int byOffsetAsc(const range_base &r1, const range_base &r2) 48 | { 49 | if (const auto d = TrivialCmp(r1.Start(), r2.Start())) 50 | return d; 51 | else 52 | return TrivialCmp(r1.len, r2.len); 53 | } 54 | 55 | range_base() 56 | : offset(0), len(0) 57 | { 58 | 59 | } 60 | 61 | range_base(const range_base &o) 62 | : offset(o.offset), len(o.len) 63 | { 64 | 65 | } 66 | 67 | range_base(const VT _o, const LT _l) 68 | : offset(_o), len(_l) 69 | { 70 | 71 | } 72 | 73 | //e.g range32_t{10,15} 74 | range_base(const std::pair p) 75 | : offset{p.first}, len{p.second - p.first} 76 | { 77 | 78 | } 79 | 80 | range_base(const LT l) 81 | : offset{0}, len{l} 82 | { 83 | 84 | } 85 | 86 | [[gnu::always_inline]] inline constexpr operator bool() const 87 | { 88 | return len; 89 | } 90 | 91 | [[gnu::always_inline]] inline bool SpansAll() const 92 | { 93 | return std::numeric_limits::min() == offset && std::numeric_limits::max() == End(); 94 | } 95 | 96 | void SetSpansAll() 97 | { 98 | offset = std::numeric_limits::min(); 99 | len = std::numeric_limits::max() - offset; 100 | } 101 | 102 | inline void Set(const VT _o, const LT _l) 103 | { 104 | offset = _o; 105 | len = _l; 106 | } 107 | 108 | void setStartEnd(const VT lo, const VT hi) 109 | { 110 | offset = lo; 111 | len = hi - lo; 112 | } 113 | 114 | range_base &operator=(const range_base &o) 115 | { 116 | offset = o.offset; 117 | len = o.len; 118 | return *this; 119 | } 120 | 121 | void SetEnd(const VT e) 122 | { 123 | len = e - offset; 124 | } 125 | 126 | // Matching SetEnd(); adjusts offset of a valid range 127 | void reset_offset(const VT start) 128 | { 129 | len = End() - start; 130 | offset = start; 131 | } 132 | 133 | [[gnu::always_inline]] inline VT constexpr Mid() const // (left + right) / 2 134 | { 135 | return offset + (len >> 1); 136 | } 137 | 138 | [[gnu::always_inline]] inline VT constexpr End() const 139 | { 140 | return offset + len; 141 | } 142 | 143 | [[gnu::always_inline]] inline VT constexpr Left() const 144 | { 145 | return offset; 146 | } 147 | 148 | [[gnu::always_inline]] inline VT constexpr Right() const 149 | { 150 | return End(); 151 | } 152 | 153 | [[gnu::always_inline]] inline VT constexpr Start() const 154 | { 155 | return offset; 156 | } 157 | 158 | // TODO: optimize 159 | // Very handy for iterating a subset e.g 160 | // for (auto i : range32(offset, perPage).ClippedTo(total) { .. } 161 | range_base ClippedTo(const VT lim) const 162 | { 163 | range_base res; 164 | 165 | res.offset = Min(offset, lim); 166 | res.len = Min(End(), lim) - res.offset; 167 | 168 | return res; 169 | } 170 | 171 | [[gnu::always_inline]] inline bool constexpr Contains(const VT o) const 172 | { 173 | return sizeof(VT) == 8 174 | ? o >= offset && o < End() 175 | : uint32_t(o - offset) < len; // o in [offset, offset+len) 176 | } 177 | 178 | [[gnu::always_inline]] inline bool constexpr operator<(const range_base &o) const 179 | { 180 | return offset < o.offset || (offset == o.offset && len < o.len); 181 | } 182 | 183 | [[gnu::always_inline]] inline bool constexpr operator<=(const range_base &o) const 184 | { 185 | return offset < o.offset || (offset == o.offset && len <= o.len); 186 | } 187 | 188 | [[gnu::always_inline]] inline bool operator>(const range_base &o) const 189 | { 190 | return offset > o.offset || (offset == o.offset && len > o.len); 191 | } 192 | 193 | [[gnu::always_inline]] inline bool constexpr operator>=(const range_base &o) const 194 | { 195 | return offset > o.offset || (offset == o.offset && len >= o.len); 196 | } 197 | 198 | 199 | template 200 | [[gnu::always_inline]] inline bool constexpr operator==(const T &o) const 201 | { 202 | return offset == o.offset && len == o.len; 203 | } 204 | 205 | template 206 | [[gnu::always_inline]] inline bool constexpr operator!=(const T &o) const 207 | { 208 | return offset != o.offset || len != o.len; 209 | } 210 | 211 | range_base Intersection(const range_base &o) const 212 | { 213 | // A range containing the indices that exist in both ranges 214 | 215 | if (End() <= o.offset || o.End() <= offset) 216 | return range_base(0, 0); 217 | else 218 | { 219 | const auto _o = Max(offset, o.offset); 220 | 221 | return range_base(_o, Min(End(), o.End()) - _o); 222 | } 223 | } 224 | 225 | void ClipOffsetTo(const VT o) 226 | { 227 | if (offset < o) 228 | { 229 | if (o >= End()) 230 | { 231 | offset = o; 232 | len = 0; 233 | } 234 | else 235 | { 236 | const auto d = o - offset; 237 | 238 | offset = o; 239 | len -= d; 240 | } 241 | } 242 | } 243 | 244 | void ClipEndTo(const VT e) 245 | { 246 | const auto end = End(); 247 | 248 | if (e < end) 249 | { 250 | if (e < offset) 251 | len = 0; 252 | else 253 | len -= end - e; 254 | } 255 | } 256 | 257 | [[gnu::always_inline]] inline bool constexpr Overlaps(const range_base &o) const 258 | { 259 | return !(End() <= o.offset || o.End() <= offset); 260 | } 261 | 262 | [[gnu::always_inline]] inline bool constexpr Contains(const range_base &o) const 263 | { 264 | return offset <= o.offset && End() >= o.End(); 265 | } 266 | 267 | range_base Union(const range_base &o) const 268 | { 269 | const auto _o = Min(offset, o.offset); 270 | 271 | return range_base(_o, Max(End(), o.End()) - _o); 272 | } 273 | 274 | uint8_t DisjointUnion(const range_base &o, range_base *out) const 275 | { 276 | const range_base *const b = out; 277 | 278 | if (offset < o.offset) 279 | { 280 | out->offset = offset; 281 | out->len = o.offset - offset; 282 | ++out; 283 | } 284 | else if (o.offset < offset) 285 | { 286 | out->offset = o.offset; 287 | out->len = offset - o.offset; 288 | ++out; 289 | } 290 | 291 | const auto thisEnd = End(), thatEnd = o.End(); 292 | 293 | if (thisEnd < thatEnd) 294 | { 295 | out->offset = thisEnd; 296 | out->len = thatEnd - thisEnd; 297 | ++out; 298 | 299 | } 300 | else if (thatEnd < thisEnd) 301 | { 302 | out->offset = thatEnd; 303 | out->len = thisEnd - thatEnd; 304 | ++out; 305 | } 306 | 307 | return out - b; 308 | } 309 | 310 | void TrimLeft(const LT span) 311 | { 312 | offset+=span; 313 | len-=span; 314 | } 315 | 316 | 317 | void Unset() 318 | { 319 | offset = 0; 320 | len = 0; 321 | } 322 | }; 323 | 324 | 325 | template 326 | static inline bool IsBetweenRange(const VT v, const VT s, const VT e) 327 | { 328 | return sizeof(VT) == 8 329 | ? v >= s && v < e 330 | : uint32_t(v - s) < (e - s); // o in [offset, offset+len) 331 | }; 332 | 333 | template 334 | static inline bool IsBetweenRangeInclusive(const VT v, const VT s, const VT e) 335 | { 336 | return sizeof(VT) == 8 337 | ? v >= s && v <= e 338 | : uint32_t(v - s) <= (e - s); // o in [offset, offset+len] 339 | }; 340 | 341 | template 342 | static inline uint32_t HashFor(const range_base &r) 343 | { 344 | return SwitchHash(&r, sizeof(r)); 345 | } 346 | 347 | 348 | using range8_t = range_base; 349 | using range16_t = range_base; 350 | using range32_t = range_base; 351 | using range64_t = range_base; 352 | using rangestr_t = range_base; // Please ust strwlen instead 353 | 354 | template 355 | static inline auto MakeRange(const VT s, const LT l) -> range_base 356 | { 357 | return {s, l}; 358 | } 359 | 360 | namespace Switch 361 | { 362 | template 363 | static inline auto make_range(const VT s, const LT l) 364 | { 365 | return range_base(s, l); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /Switch/network.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static inline constexpr uint32_t _ntohl(const uint32_t orig) 13 | { 14 | return ({ 15 | const auto b1 = (orig >> 24) & 255; 16 | const auto b2 = (orig >> 16) & 255; 17 | const auto b3 = (orig >> 8) & 255; 18 | const auto b4 = (orig & 255); 19 | 20 | (b4 << 24) | (b3 << 16) | (b2 << 8) | b1; 21 | }); 22 | } 23 | static inline constexpr uint32_t IP4Addr(const uint8_t a, const uint8_t b, const uint8_t c, const uint8_t d) 24 | { 25 | return _ntohl((a << 24) | (b << 16) | (c << 8) | d); 26 | } 27 | 28 | namespace Switch 29 | { 30 | struct endpoint 31 | { 32 | uint32_t addr4; 33 | uint16_t port; 34 | 35 | void unset() 36 | { 37 | addr4 = INADDR_NONE; // 255.255.255.255 38 | port = 0; 39 | } 40 | 41 | inline operator bool() const 42 | { 43 | return addr4 != INADDR_NONE && port; 44 | } 45 | 46 | void set(const uint32_t a, const uint16_t p) 47 | { 48 | addr4 = a; 49 | port = p; 50 | } 51 | 52 | auto operator==(const endpoint &o) const 53 | { 54 | return addr4 == o.addr4 && port == o.port; 55 | } 56 | 57 | auto operator!=(const endpoint &o) const 58 | { 59 | return addr4 != o.addr4 || port != o.port; 60 | } 61 | 62 | operator uint32_t() const 63 | { 64 | return addr4; 65 | } 66 | 67 | auto operator<(const endpoint &o) const 68 | { 69 | return addr4 < o.addr4 || (addr4 == o.addr4 && port < o.port); 70 | } 71 | }; 72 | 73 | inline int SetNoDelay(int fd, const int v) 74 | { 75 | // Nagle's algorithm 76 | return setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(int)); 77 | } 78 | 79 | inline int SetTCPCork(int fd, const int v) 80 | { 81 | #ifdef __linux__ 82 | return setsockopt(fd, IPPROTO_TCP, TCP_CORK, &v, sizeof(v)); 83 | #else 84 | return setsockopt(fd, IPPROTO_TCP, TCP_NOPUSH, &v, sizeof(v)); 85 | #endif 86 | } 87 | 88 | inline int SetReuseAddr(int fd, const int v) 89 | { 90 | return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)); 91 | } 92 | 93 | inline uint32_t ParseHostAddress(const strwlen8_t repr, bool &succ) 94 | { 95 | const char *p = repr.p, *const end = repr.End(); 96 | uint32_t res, octet; 97 | uint8_t *const octets = (uint8_t *)&res; 98 | 99 | for (uint32_t i{0};; ++i, ++p) 100 | { 101 | if (p == end || !isdigit(*p)) 102 | { 103 | succ = false; 104 | return 0; 105 | } 106 | 107 | for (octet = *p++ - '0'; p != end && isdigit(*p); ++p) 108 | octet = octet * 10 + (*p - '0'); 109 | 110 | if (unlikely(octet > 255)) 111 | { 112 | succ = false; 113 | return 0; 114 | } 115 | 116 | octets[i] = octet; 117 | if (i != 3) 118 | { 119 | if (p == end || *p != '.') 120 | { 121 | succ = false; 122 | return 0; 123 | } 124 | } 125 | else 126 | { 127 | if (p != end && *p) 128 | { 129 | succ = false; 130 | return 0; 131 | } 132 | else 133 | { 134 | succ = true; 135 | return res; 136 | } 137 | } 138 | } 139 | } 140 | 141 | inline endpoint ParseSrvEndpoint(strwlen32_t addr, const strwlen8_t proto, const uint16_t srvPort) 142 | { 143 | endpoint res; 144 | 145 | if (addr.BeginsWithNoCase(proto.p, proto.len) && addr.SuffixFrom(proto.len).BeginsWith(_S("://"))) 146 | addr.StripPrefix(proto.len + 3); 147 | 148 | const auto r = addr.Divided(':'); 149 | 150 | if (!r.first) 151 | res.addr4 = ntohl(INADDR_ANY); 152 | else if (r.first.len > 128) 153 | return {0, 0}; 154 | else 155 | { 156 | bool succ; 157 | 158 | res.addr4 = ParseHostAddress({r.first.p, r.first.len}, succ); 159 | if (!succ && r.first.len < 250) 160 | { 161 | char n[256], buf[512]; 162 | struct hostent ret, *he; 163 | int errno_; 164 | 165 | r.first.ToCString(n); 166 | if (gethostbyname_r(n, &ret, buf, sizeof(buf), &he, &errno_) == 0 && he && he->h_addrtype == AF_INET && he->h_length) 167 | res.addr4 = *(uint32_t *)he->h_addr_list[0]; 168 | else 169 | return {0, 0}; 170 | } 171 | else 172 | return {0, 0}; 173 | } 174 | 175 | if (r.second) 176 | { 177 | if (!r.second.IsDigits()) 178 | return {0, 0}; 179 | 180 | const auto port = r.second.AsUint32(); 181 | if (port > 65536) 182 | return {0, 0}; 183 | else 184 | res.port = port; 185 | } 186 | else 187 | res.port = srvPort; 188 | 189 | return res; 190 | } 191 | } 192 | 193 | class EPoller 194 | { 195 | private: 196 | int epollFd; 197 | struct epoll_event *returnedEvents; 198 | uint32_t returnedEventsSize, maxReturnedEvents; 199 | struct epoll_event _localEventsStorage[8]; /* this helps if we wish to avoid allocating from the heap */ 200 | 201 | public: 202 | EPoller(const uint32_t _maxReturnedEvents = 256) 203 | : maxReturnedEvents{_maxReturnedEvents} 204 | { 205 | epollFd = epoll_create(4096); 206 | returnedEvents = nullptr; 207 | returnedEventsSize = 0; 208 | returnedEvents = (epoll_event *)malloc(sizeof(epoll_event) * maxReturnedEvents); 209 | } 210 | 211 | ~EPoller(void) 212 | { 213 | if (likely(epollFd != -1)) 214 | close(epollFd); 215 | 216 | if (returnedEvents && returnedEvents != _localEventsStorage) 217 | free(returnedEvents); 218 | } 219 | 220 | [[gnu::always_inline]] static auto _epoll_ctl(int epfd, int op, int fd, struct epoll_event *const event) 221 | { 222 | return epoll_ctl(epfd, op, fd, event); 223 | } 224 | 225 | inline void AddFd(int fd, const uint32_t events) 226 | { 227 | struct epoll_event e; 228 | 229 | e.events = events; 230 | e.data.fd = fd; 231 | 232 | _epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &e); 233 | } 234 | 235 | inline void AddFd(int fd, const uint32_t events, void *uData) 236 | { 237 | struct epoll_event e; 238 | 239 | e.events = events; 240 | e.data.ptr = uData; 241 | 242 | _epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &e); 243 | } 244 | 245 | inline void DelFd(int fd) 246 | { 247 | _epoll_ctl(epollFd, EPOLL_CTL_DEL, fd, nullptr); 248 | } 249 | 250 | inline void SetEvents(int fd, const uint32_t events) 251 | { 252 | struct epoll_event e; 253 | 254 | e.events = events; 255 | e.data.fd = fd; 256 | 257 | _epoll_ctl(epollFd, EPOLL_CTL_MOD, fd, &e); 258 | } 259 | 260 | inline void SetDataAndEvents(int fd, void *data, const uint32_t events) 261 | { 262 | struct epoll_event e; 263 | 264 | e.events = events; 265 | e.data.ptr = data; 266 | 267 | _epoll_ctl(epollFd, EPOLL_CTL_MOD, fd, &e); 268 | } 269 | 270 | [[gnu::always_inline]] int Poll(const int timeoutInMSecs) 271 | { 272 | return epoll_wait(epollFd, returnedEvents, maxReturnedEvents, timeoutInMSecs); 273 | } 274 | 275 | [[gnu::always_inline]] const struct epoll_event *Events(void) const 276 | { 277 | return returnedEvents; 278 | } 279 | }; 280 | 281 | namespace std 282 | { 283 | template<> 284 | struct hash 285 | { 286 | using argument_type = Switch::endpoint; 287 | using result_type = std::size_t; 288 | 289 | result_type operator()(const argument_type &e) const 290 | { 291 | return std::hash{}((uint64_t(e.addr4) << 32u) | e.port); 292 | } 293 | }; 294 | } 295 | 296 | static inline void PrintImpl(Buffer &v, const Switch::endpoint &e) 297 | { 298 | const uint8_t *const octets = (uint8_t *)&e.addr4; 299 | 300 | v.AppendFmt("%u.%u.%u.%u:%u", octets[0], octets[1], octets[2], octets[3], e.port); 301 | } 302 | 303 | -------------------------------------------------------------------------------- /coroutines.h: -------------------------------------------------------------------------------- 1 | /* 2 | * coroutines.h 3 | * 4 | * See https://medium.com/software-development-2/coroutines-and-fibers-why-and-when-5798f08464fd#.hsfh18d5f 5 | * 6 | * The libc get/make/swap-context() API is somewhat more powerful, for makecontext() can setup as trampoline a function 7 | * that accepts arbitrary number of arguments. The downside is complexity. 8 | * Originally, this library provided alternative implementations to those APIs. It was only storing and restoring the regs ABI stipulates we should track, doing away with 9 | * signals mask and floating points regs. It was working fine. 10 | * 11 | * I don't really need that - I only need is a single argument, though it should be pretty straight forward to support arbitrary number of arguments 12 | * like makecontext() does, but the overhead and complexity required to implement that do not justify pursuing this change. 13 | * See https://github.com/lattera/glibc /sysdeps/unix/sysv/linux/x86_64/ for glibc implementation specifics. 14 | * 15 | * Instead, what makes more sense and is infinitely simpler is to use a static thread_local for the trampoline's context. 16 | * 17 | * We could also support more platforms/arcs, and other means of control transfer. 18 | * As explained by https://github.com/clibs/coro/coro.h, other approaches include: 19 | * - use of SUSv2's get/set/swap/makecontext API. Supported by some systems, and slow 20 | * - use of SUSv2's setjmp/longjmp and sigaltstack APIs to specify a stack for a signal handler's stack. Much slower to create coro than ucontext APIs, but somewhat faster to switch between them. 21 | * We can and should use the alternative _setjmp/_longjmp instead (will not incur the system call to save and restore the siganls mask) - which results in much higher performance. 22 | * This is supported by all Unix flavors 23 | * - variant for glibc <= 2.1, for Linux, which is very fast, but requires older glibc. This comes down to use of a different use of sigsetjmp() and siglongjmp() APIs 24 | * - use of the PThreads API. This is very slow and better be avoided 25 | * If you need to support multiple archs, use the excellent https://github.com/clibs/coro 26 | * 27 | * I suppose I could just provide support to other unix flavors via getcontext()/makecontext()/setcontext() for initialization and setjmp()/longjmp() for switches which is faster 28 | * until assembly optimized implementations to coro_transfer() and coro_resume() can be implemented. (see SeaStar thread_context design) 29 | * 30 | * 31 | * PERFORMANCE 32 | * On a Xeon at 2Ghz, we get about 21million context switches/second. This is far more than we would need, assuming appropriate use of coroutines/fibers and context switching 33 | * where it makes sense(e.g before blocking ops, or when a coro has been running for too long), which means there will be an infrequent need for context switching, it should 34 | * be the 'solution' to achieving very high performance. 35 | * 36 | */ 37 | #pragma once 38 | #if !defined(__amd64) || !defined(__linux) 39 | // TODO: support i386-linux(trivial). Support for other unix flavors via makecontext(), setjmp() and longjmp(). (that's what Seastar does it) 40 | #error Unsupported platform/arch 41 | #endif 42 | #define VALGRIND_SUPPORT 1 43 | #include 44 | #ifdef VALGRIND_SUPPORT 45 | #include 46 | #endif 47 | #include 48 | #include 49 | #include 50 | #include 51 | 52 | #pragma mark Coroutines 53 | struct coro_stack 54 | { 55 | void *stack; 56 | size_t size; 57 | uint8_t flags{0}; 58 | #ifdef VALGRIND_SUPPORT 59 | int valgrindId; 60 | #endif 61 | 62 | bool alloc(size_t hint, const uint8_t protect = false); 63 | void release(); 64 | bool set(void *const p, const size_t s); 65 | }; 66 | 67 | struct coro_ctx 68 | { 69 | void **sp; // must be offset 0 in coro_ctx; see coro_transfer and coro_resume impl. 70 | }; 71 | 72 | // We absolutely need to make sure the compiler won't inline those (e.g -O2 or higher) 73 | extern "C" { 74 | extern void __attribute__((__noinline__, __regparm__(2))) 75 | coro_transfer(coro_ctx *prev /* rdi */, coro_ctx *next /* rsi */); 76 | 77 | extern void __attribute__((__noinline__, __regparm__(1))) 78 | coro_resume(coro_ctx *ctx /* rdi */); 79 | }; 80 | 81 | // http://wiki.osdev.org/System_V_ABI 82 | // functions are called using the CALL instruction, which pushes the address of the next instruction to the stack, and then jumps to the operand 83 | // functions return to the caller using the RET instruction, which pops a value from the stack and jumps to it. 84 | // 85 | // The stack is 16-byte aligned just before the call instruction is called 86 | // 87 | // Functions preserve rbx, rsp, rbp, r12, r13, r14 and r15; while rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11 are scratch regs. 88 | // The return value is stored in the rax reg, or if it is a 128-bit value, then the higher 64-bits go in rdx 89 | // 90 | // Optionally, functions push rbp such that the caller-return-rip is 8 bytes above it, and erp to the addres of the saved rbp. this allows iterating the existing stack frames. 91 | void coro_init_with_trampoline(coro_ctx *ctx, void (*func)(void *), void *arg, coro_stack *const coroStack, void (*trampoline)()); 92 | void coro_init(coro_ctx *ctx, void (*func)(void *), void *arg, coro_stack *const coroStack); 93 | 94 | #pragma mark Fibers 95 | // We need both different prios and time tracking, but let's make this tunable here 96 | #define HAVE_FIBER_PRIOS 1 // Support different priorities 97 | #define HAVE_FIBER_TS 1 // track coroutines timestamps(running since, ended at) 98 | 99 | namespace Fibers 100 | { 101 | void FiberTrampoline(); 102 | 103 | struct fiber 104 | { 105 | coro_ctx ctx; 106 | coro_stack stack; 107 | 108 | #ifdef HAVE_FIBER_PRIOS 109 | // fibers of priority X will be scheduled before those of priority > X 110 | // 0 is the highest prio, 7 is the lowest 111 | uint8_t prio; 112 | #endif 113 | 114 | #ifdef HAVE_FIBER_TS 115 | uint64_t ts; // if currently running, when it was scheduled, otherwise, when it stopped running 116 | #endif 117 | 118 | switch_dlist list; // in ready list for its priority 119 | fiber *awaits; // if set, another fiber is waiting for this fiber to complete 120 | std::function L; 121 | }; 122 | 123 | // this is go's scheduler implementation 124 | // https://github.com/golang/go/blob/master/src/runtime/proc.go 125 | // findrunnable() is very interesting. Will GC if needed. 126 | // It first checks the local runq. If it is empty, it then checks the global run queue. 127 | // It then check netpoll, and then randomly steals from other Ps (see M, N, P) 128 | struct Scheduler 129 | { 130 | public: 131 | fiber *cur{nullptr}; 132 | 133 | private: 134 | #ifdef HAVE_FIBER_PRIOS 135 | uint8_t readyMask{0}; 136 | #endif 137 | 138 | public: 139 | uint32_t readyCnt{0}; 140 | // For performance measurements 141 | uint32_t ctxSwitches{0}; 142 | 143 | private: 144 | #ifdef HAVE_FIBER_PRIOS 145 | switch_dlist ready[8]; 146 | #else 147 | switch_dlist ready; 148 | #endif 149 | 150 | public: 151 | uint32_t aliveCnt{0}; // includes suspended and not runnable count 152 | simple_allocator allocator; 153 | Switch::vector freeFibers; 154 | Switch::vector freeStacks; 155 | 156 | private: 157 | fiber *get_fiber(); 158 | 159 | void put_fiber(fiber *const f); 160 | 161 | // Makes fiber `f` runnable. That is, it will be eventually schedulef by trasnfering execution control to it 162 | void make_ready(fiber *const f); 163 | 164 | // Here you could roll your own which would e.g also consider 165 | // fibers that haven't had a chance to run for too long, or always schedule a particular fiber regardless 166 | // of priorities first, etc. Most likely should be made virtual so 167 | // that other scheduler implementations can do what's needed 168 | fiber *next_ready(); 169 | 170 | public: 171 | inline auto current() const noexcept 172 | { 173 | return cur; 174 | } 175 | 176 | Scheduler() 177 | { 178 | #ifdef HAVE_FIBER_PRIOS 179 | for (auto &it : ready) 180 | switch_dlist_init(&it); 181 | #else 182 | switch_dlist_init(&ready); 183 | #endif 184 | } 185 | 186 | ~Scheduler(); 187 | 188 | template 189 | void schedule(F &&l, const uint8_t prio = 4) 190 | { 191 | auto *const f = get_fiber(); 192 | 193 | Drequire(f); 194 | f->L = std::move(l); 195 | // We are using our own trampoline, just so that we we 'll save one un-necessary call 196 | // from the standard coroInit trampoline to another. We also don't need to provide a func.ptr 197 | coro_init_with_trampoline(&f->ctx, nullptr, f, &f->stack, FiberTrampoline); 198 | f->awaits = nullptr; 199 | 200 | #ifdef HAVE_FIBER_PRIOS 201 | f->prio = prio; 202 | #endif 203 | 204 | make_ready(f); 205 | } 206 | 207 | // Puts current fiber to sleep. 208 | // Something will need to wake it up later. Typically, you 'd obtain curent active fiber (e.g scheduler.Cur() call) 209 | // and have something later invoke scheduler.Resume() on it, so that it can be rescheduled. 210 | // 211 | // For example, if you are about to perform some expensive I/O op, you may want to do it off thread and then resume when 212 | // you are done, e.g 213 | // { 214 | // auto cur = scheduler.current(); 215 | // 216 | // WorkOffThread(cur); 217 | // suspend(); 218 | // } 219 | void suspend(); 220 | 221 | void resume(fiber *); 222 | 223 | // Yield control to another runnable fiber, but also make this current fiber runnable 224 | // so that it will get a chance to run again as soon as possible based on the scheduler semantics. 225 | void yield(); 226 | 227 | // Convenience method. Schedule a fiber and immediately yield 228 | template 229 | void yield(F &&f, const uint8_t prio = 4) 230 | { 231 | schedule(std::move(f), prio); 232 | yield(); 233 | } 234 | 235 | // This should be invoked in the execution context of a 'scheduler', that is, not in the context 236 | // of a fiber (though a scheduler can of course be implemented as a fiber). 237 | // Control will be transfered to the next runnable fiber. 238 | void yield_from_main(coro_ctx *const ctx); 239 | 240 | void yield_from_main(); 241 | 242 | // Make `f` ready, and schedule the next runnable 243 | // It will suspend the current fiber and will then schedule the next runnable. Once 244 | // the scheduled `f` completes, the now suspended fiber will be made runnable again and potentially immediately rescheduled 245 | // 246 | // See "An Introduction to C++ Coroutines by James McNellis" https://youtu.be/YYtzQ355_Co?t=770 247 | // where he is demonstrating that 248 | // { 249 | // auto result = co_await expression; 250 | // } 251 | // is translated to 252 | // { 253 | // auto &&__a = expression; 254 | // 255 | // if (!__a.await_ready()) 256 | // { 257 | // __a.await_suspend(coroutine-handle); 258 | // // .. suspend/resume points .. 259 | // } 260 | // auto result = __a.await_resume(); 261 | // } 262 | // 263 | // 264 | // Conversely, co_return y is translated to ( see https://youtu.be/YYtzQ355_Co?t=2727 ) 265 | // { 266 | // __promise.return_value(x); 267 | // goto __final_suspend_label; 268 | // } 269 | // and co_yield z to 270 | // { 271 | // co_await __promise.yield_value(z); 272 | // } 273 | void await_fiber(fiber *const f); 274 | 275 | template 276 | void await(T &&l, const uint8_t prio = 4) 277 | { 278 | auto *const f = get_fiber(); 279 | 280 | #ifdef HAVE_FIBER_PRIOS 281 | f->prio = prio; 282 | #endif 283 | f->L = std::move(l); 284 | await_fiber(f); 285 | } 286 | 287 | // Whenever a fiber function returns(i.e fiber has exited) 288 | // this is invoked. 289 | void on_fiber_exit(); 290 | }; 291 | 292 | extern thread_local coro_ctx mainCoro; 293 | extern thread_local Scheduler scheduler; 294 | } 295 | -------------------------------------------------------------------------------- /Switch/switch_mallocators.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "switch_ll.h" 5 | #include "ansifmt.h" 6 | 7 | struct simple_allocator 8 | { 9 | enum class BackingStore : uint8_t 10 | { 11 | MMAP = 1 12 | }; 13 | 14 | int8_t *first_{nullptr}, *last_{nullptr}, *cur_{nullptr}; 15 | const uint32_t bankCapacity_; 16 | uint32_t curBankUtilization_; 17 | 18 | static constexpr uint32_t buildBankCapacity(const uint32_t v, const bool f) 19 | { 20 | return v | (uint32_t(f) << 31); 21 | } 22 | 23 | static inline int8_t *next(int8_t *const ptr) 24 | { 25 | return (int8_t *)*(uintptr_t *)ptr; 26 | } 27 | 28 | static inline void setNext(int8_t *const a, const int8_t *const next) 29 | { 30 | *(uintptr_t *)a = uintptr_t(next); 31 | } 32 | 33 | inline uint32_t bankCapacity() const 34 | { 35 | return bankSize() - sizeof(uintptr_t); 36 | } 37 | 38 | inline uint32_t bankSize() const 39 | { 40 | return bankCapacity_ & INT32_MAX; 41 | } 42 | 43 | explicit simple_allocator(simple_allocator &&o) 44 | : bankCapacity_(o.bankCapacity_) 45 | { 46 | first_ = o.first_; 47 | last_ = o.last_; 48 | cur_ = o.cur_; 49 | 50 | curBankUtilization_ = o.curBankUtilization_; 51 | 52 | // we can't touch o again 53 | o.first_ = o.last_ = o.cur_ = nullptr; 54 | o.curBankUtilization_ = 0; 55 | } 56 | 57 | int MLock() 58 | { 59 | const auto capacity = bankSize(); 60 | 61 | for (auto it = first_; it; it = next(it)) 62 | { 63 | if (const auto r = mlock(it, capacity)) 64 | return r; 65 | } 66 | return 0; 67 | } 68 | 69 | int MUnlock() 70 | { 71 | const auto capacity = bankSize(); 72 | 73 | for (auto it = first_; it; it = next(it)) 74 | { 75 | if (const auto r = munlock(it, capacity)) 76 | return r; 77 | } 78 | 79 | return 0; 80 | } 81 | 82 | // if you want to make sure that a page holds at least e.g sizeof(foo), use simple_allocator::minBankSizeForSize(sizeof(foo)) 83 | // This is because we reserve sizeof(uintptr_t) first bytes from a bank 84 | static inline size_t minBankSizeForSize(const uint32_t s) 85 | { 86 | return s + sizeof(uintptr_t); 87 | } 88 | 89 | simple_allocator(const uint32_t bc = 1024 * 1024) 90 | : bankCapacity_(buildBankCapacity(goodMallocSize(Clamp(RoundToMultiple(bc, 8), 64, 64 * 1024 * 1024)), false)) 91 | { 92 | curBankUtilization_ = bankSize(); 93 | } 94 | 95 | // Using a mmmap backed allocator is a _great_ way to deal with memory utilizatiomm issues. 96 | // Whenever stdlib (or any other allocator) allocates memmory, it ends up reserving a VMA via mmap, and then provides from that area 97 | // to callee, until its freed. While 1+ chunks from the same VMA are not freed(pending allocations from that VMA), the allocator cannot munmap() the 98 | // VMA, so it's very possible that if you e.g allocate many small chunks, then a big one, then some other one etc, even if only one chunk is allocated 99 | // from a VMA, it never gets dropped so RES goes up. 100 | // Using this mmmap backed custom allocator helps, because when we are done, we just munmap() that one VMA and we are done. 101 | // e.g simple_allocator(size, simple_allocator::BackingStore{}) 102 | simple_allocator(const uint32_t bc, const enum BackingStore) 103 | : bankCapacity_(buildBankCapacity(RoundToMultiple(bc, getpagesize()), true)) 104 | { 105 | curBankUtilization_ = bankSize(); 106 | } 107 | 108 | ~simple_allocator() 109 | { 110 | _FlushBanks(); 111 | } 112 | 113 | simple_allocator(const simple_allocator&) = delete; 114 | simple_allocator &operator=(const simple_allocator &) = delete; 115 | simple_allocator &operator=(simple_allocator&&) = delete; 116 | 117 | inline bool canFitInCurBank(const uint32_t size) const // for debugging 118 | { 119 | return curBankUtilization_ + size <= bankSize(); 120 | } 121 | 122 | auto curBankAvail__() const // for debugging 123 | { 124 | return !first_ ? bankCapacity() : bankSize() - curBankUtilization_; 125 | } 126 | 127 | auto banksCount() const 128 | { 129 | uint32_t cnt{0}; 130 | 131 | for (auto it = first_; it; it = next(it)) 132 | ++cnt; 133 | return cnt; 134 | } 135 | 136 | size_t footprint() const 137 | { 138 | return banksCount() * bankSize() + sizeof(*this); 139 | } 140 | 141 | void allocNewBank_(const uint32_t bs) 142 | { 143 | 144 | if (cur_ == last_) 145 | { 146 | int8_t *newBank; 147 | 148 | if (bankCapacity_ & (1u << 31)) 149 | { 150 | 151 | newBank = (int8_t *)mmap(nullptr, bs, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 152 | 153 | if (unlikely(newBank == MAP_FAILED)) 154 | throw std::system_error({}, "mmap() failed"); 155 | else 156 | { 157 | #ifdef MADV_NOHUGEPAGE 158 | // This is important, read: 159 | // Via @mattsta: https://twitter.com/mattsta/status/529327782351097856 160 | // > Other products/vendors that recommend to disable THP: cloudera, vertica, varnish, mongo, websphere, maridb, intel, ... 161 | // Redis now recomends: 162 | // echo never > /sys/kernel/mm/transparent_hugepage/enabled 163 | // Also refs: 164 | // - http://dev.nuodb.com/techblog/linux-transparent-huge-pages-jemalloc-and-nuodb 165 | // - http://www.percona.com/blog/2014/07/23/why-tokudb-hates-transparent-hugepages/ 166 | // > The classical problem with memory allocators is fragmentation. If you allocated a 2MB chunk from the OS 167 | // > typically using mmap(), as the process runs it's likely some of that 2MB memory block will become free but 168 | // > not all of it, hence it can't be given back to the OS completely. jamalloc and other allocators use a clever trick 169 | // > where they use madvise(..., MADV_DONTNEED) to give back portions of memory allocated in such a way back to the OS. 170 | // 171 | // > With THP, the OS (and the CPU, really) work with pages of much larger size which only can be unmapped from the address 172 | // > space in its entirety - which does not work when smaller objects are freed which produce smaller free "holes" 173 | // > As a result, without being able to free memory efficientl, the mount of allocated memory may go up unbound until 174 | // > the process starts to swap out - and in the end being killed by the OOMK at least under some workloads. 175 | // > This is not a behavior you want to see from a database server. 176 | madvise(newBank, bs, MADV_NOHUGEPAGE); 177 | #endif 178 | } 179 | } 180 | else 181 | { 182 | try 183 | { 184 | newBank = (int8_t *)malloc(bs); 185 | } 186 | catch (...) 187 | { 188 | newBank = nullptr; 189 | } 190 | 191 | if (unlikely(!newBank)) 192 | throw std::system_error({}, "Unable to allocate memory"); 193 | 194 | // We can't use capacity = malloc_usable_size(curBank) here, because we won't always get the same 195 | // usable size for each bank allocated (no guarantees, may be more or less). So, unless we keep 196 | // track of usability per bank, we can't use this. 197 | } 198 | 199 | setNext(newBank, nullptr); 200 | 201 | if (last_) 202 | setNext(last_, newBank); 203 | else 204 | first_ = newBank; 205 | 206 | cur_ = last_ = newBank; 207 | } 208 | else 209 | { 210 | cur_ = next(cur_); 211 | } 212 | } 213 | 214 | void *Alloc(const uint32_t size) 215 | { 216 | const auto bs = bankSize(); 217 | 218 | // micro-optimization: we expect to be able to use current bank 219 | auto *const res = reinterpret_cast(cur_ + curBankUtilization_); 220 | 221 | curBankUtilization_ += size; 222 | if (unlikely(curBankUtilization_ > bs)) 223 | { 224 | if (unlikely(size > bankCapacity())) 225 | throw std::range_error("Requested more bytes than bank's capacity"); 226 | 227 | // it is important that we do not inline allocNewBank_() code here 228 | // in order to reduce ICache miss rate -- because we almost never need to execute that code 229 | allocNewBank_(bs); 230 | curBankUtilization_ = size + sizeof(uintptr_t); 231 | 232 | return reinterpret_cast(cur_ + sizeof(uintptr_t)); 233 | } 234 | else 235 | return res; 236 | } 237 | 238 | // allocWithLock() and other WithLock() methods are very handy 239 | // if you really want to share an allocator among multiple threads, in a thread safe manner 240 | // this is not recommended, however it may be a good idea if the contention is low and if memory fragementation and pressure is causing problems 241 | template 242 | void *allocWithLock(const uint32_t size, T &lock) 243 | { 244 | const auto bs = bankSize(); 245 | 246 | if (unlikely(curBankUtilization_ + size > bs)) 247 | { 248 | if (unlikely(size > bankCapacity())) 249 | throw std::range_error("Requested more bytes than bank's capacity"); 250 | 251 | lock.lock(); 252 | 253 | allocNewBank_(bs); 254 | curBankUtilization_ = size + sizeof(uintptr_t); 255 | 256 | auto *const res = reinterpret_cast(cur_ + sizeof(uintptr_t)); 257 | 258 | lock.unlock(); 259 | 260 | return res; 261 | } 262 | else 263 | { 264 | lock.lock(); 265 | 266 | char *const ret = reinterpret_cast(cur_ + curBankUtilization_); 267 | 268 | curBankUtilization_ += size; 269 | 270 | lock.unlock(); 271 | 272 | return ret; 273 | } 274 | } 275 | 276 | inline void Reuse() 277 | { 278 | if (first_) 279 | { 280 | cur_ = first_; 281 | curBankUtilization_ = sizeof(uintptr_t); 282 | } 283 | else 284 | { 285 | cur_ = nullptr; 286 | curBankUtilization_ = bankSize(); 287 | } 288 | } 289 | 290 | void _FlushBanks() 291 | { 292 | if (bankCapacity_ & (1u << 31)) 293 | { 294 | const auto capacity = bankSize(); 295 | 296 | for (auto it = first_; it;) 297 | { 298 | auto n = next(it); 299 | 300 | madvise(it, capacity, MADV_DONTNEED); 301 | munmap(it, capacity); 302 | it = n; 303 | } 304 | } 305 | else 306 | { 307 | for (auto it = first_; it;) 308 | { 309 | auto n = next(it); 310 | 311 | std::free(it); 312 | it = n; 313 | } 314 | } 315 | 316 | first_ = last_ = cur_ = nullptr; 317 | } 318 | 319 | void Clean() 320 | { 321 | _FlushBanks(); 322 | 323 | curBankUtilization_ = bankSize(); 324 | cur_ = first_ = last_ = nullptr; 325 | } 326 | 327 | void Reset() 328 | { 329 | _FlushBanks(); 330 | 331 | curBankUtilization_ = bankSize(); 332 | cur_ = first_ = last_ = nullptr; 333 | } 334 | 335 | inline void *CAlloc(const uint32_t size) 336 | { 337 | void *const p = Alloc(size); 338 | 339 | if (likely(p)) 340 | memset(p, 0, size); 341 | 342 | return p; 343 | } 344 | 345 | template 346 | T *CopyOf(const T *const v, const uint32_t n) 347 | { 348 | T *const res = (T *)Alloc(n * sizeof(T)); 349 | 350 | Drequire(res); 351 | memcpy(res, v, n * sizeof(T)); 352 | return res; 353 | } 354 | 355 | template 356 | T *CopyOf(const T *const v) 357 | { 358 | return CopyOf(v, 1); 359 | } 360 | 361 | template 362 | inline T *New() 363 | { 364 | return (T *)Alloc(sizeof(T)); 365 | } 366 | 367 | template 368 | inline T *Alloc() 369 | { 370 | return (T *)Alloc(sizeof(T)); 371 | } 372 | 373 | template 374 | inline T *Alloc(const uint32_t cnt) 375 | { 376 | return (T *)Alloc(sizeof(T) * cnt); 377 | } 378 | 379 | template 380 | inline T *construct(Args &&... args) 381 | { 382 | return new (Alloc(sizeof(T))) T(std::forward(args)...); 383 | } 384 | 385 | template 386 | inline T *constructWithLock(LT &lock, Args &&... args) 387 | { 388 | return new (allocWithLock(sizeof(T), lock)) T(std::forward(args)...); 389 | } 390 | 391 | // http://en.cppreference.com/w/cpp/memory/allocator/destroy 392 | template 393 | inline void destroy(T *const ptr) 394 | { 395 | ptr->~T(); 396 | } 397 | }; 398 | -------------------------------------------------------------------------------- /coroutines.cpp: -------------------------------------------------------------------------------- 1 | #include "coroutines.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | thread_local coro_ctx Fibers::mainCoro; 8 | thread_local Fibers::Scheduler Fibers::scheduler; 9 | 10 | #pragma mark Coroutines 11 | // A coroutine trampoline function _never_ returns 12 | // When the function that implements the coroutine returns, we are in the context of the coroutine (stack) 13 | // So, to return control to the callee, we 'd need to transfer control to the calle's context(i.e restore regs etc) 14 | // 15 | // This makes little to no sense though. 16 | // Instead, because we know the coro has ended, we should resume a coro (if one) waiting for it, or just resume the next runnable coro. 17 | // There should be always be one runnable coro, at least the one associated with the main run-loop of the scheduler. 18 | // 19 | // In practice, we 'd have a coro_ctx used by the scheduler we could always return to -- and the scheduler should check if its the only registered(ready or stopped) coro 20 | // If not, it should be able to exit 21 | static void coroInit() 22 | { 23 | // We have stored(reg, arg) in the stack here so that when control was transfered over to this trampoline, we 'd know 24 | // what to do. There are probably better ways to do this, but for now, this will do fine. 25 | // XXX: this doesn't work if -O1 or higher, because the stack structure is different than what we expect here. 26 | // Should be easy to fix (store func,arg elsewhere) 27 | // http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/ 28 | void (*func)(void *); 29 | void *arg; 30 | 31 | asm volatile( 32 | "movq 16(%%rbp), %%rax\n" 33 | "movq %%rax, %0\n" 34 | "movq 8(%%rbp), %%rax\n" 35 | "movq %%rax, %1\n" 36 | : "=m"(func), "=m"(arg)::"%rax"); 37 | 38 | #if __GCC_HAVE_DWARF2_CFI_ASM && __amd64 39 | asm(".cfi_undefined rip"); 40 | #endif 41 | 42 | func(arg); 43 | 44 | // Your trampoline should e.g coro_resume(&corosSchedulerCtx); 45 | 46 | // We can never return from here 47 | // Just schedule next, if none, schedule the 'main'(scheduler) coro 48 | // We are supposed to _DESTROY_ coro (return to free list) 49 | // We could have used coro_transfer() before we do that, but for performance reasons, use 50 | // coro_resume() which doesn't need to save state 51 | // e.g coro_resume(&corosSchedulerCtx); 52 | assume_unreachable(); 53 | } 54 | 55 | void coro_init(coro_ctx *ctx, void (*func)(void *), void *arg, coro_stack *const coroStack) 56 | { 57 | coro_init_with_trampoline(ctx, func, arg, coroStack, coroInit); 58 | } 59 | 60 | // How many guard stack pages to use. Accesses to those (below the stack) will result in segment violations (as opposed to being silently ignored) 61 | #define GUARD_PAGES_CNT 2 62 | 63 | bool coro_stack::alloc(size_t hint, const uint8_t protect) 64 | { 65 | static const auto pageSize = sysconf(_SC_PAGESIZE); 66 | 67 | // Round up to multiple of page size 68 | size = (hint + pageSize - 1) & ~(pageSize - 1); 69 | if (protect) 70 | { 71 | // if we are going to use posix_memalign(), size must be multiple of page size 72 | // for now, we are just going to use mmap() 73 | // 74 | // 'waste' some guard pages below the stack -- protect it so that rogue accesses will be caught 75 | stack = mmap(nullptr, size + GUARD_PAGES_CNT * pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 76 | 77 | if (stack == MAP_FAILED) 78 | return false; 79 | else 80 | { 81 | #if GUARD_PAGES_CNT > 0 82 | // can't touch this 83 | mprotect(stack, GUARD_PAGES_CNT * pageSize, PROT_NONE); 84 | #endif 85 | 86 | flags = 3; 87 | #ifdef VALGRIND_SUPPORT 88 | valgrindId = VALGRIND_STACK_REGISTER((char *)stack, (char *)stack + (size + GUARD_PAGES_CNT * pageSize)); 89 | #endif 90 | stack = ((char *)stack) + GUARD_PAGES_CNT * pageSize; 91 | } 92 | } 93 | else 94 | { 95 | stack = malloc(size); 96 | 97 | if (!stack) 98 | return false; 99 | 100 | flags = 1; 101 | #ifdef VALGRIND_SUPPORT 102 | valgrindId = VALGRIND_STACK_REGISTER((char *)stack, (char *)stack + size); 103 | #endif 104 | } 105 | 106 | return true; 107 | } 108 | 109 | void coro_stack::release() 110 | { 111 | #ifdef VALGRIND_SUPPORT 112 | VALGRIND_STACK_DEREGISTER(valgrindId); 113 | #endif 114 | 115 | if (flags) 116 | { 117 | if (flags & 2) 118 | { 119 | static const auto pageSize = sysconf(_SC_PAGESIZE); 120 | 121 | munmap((char *)stack - GUARD_PAGES_CNT * pageSize, size + GUARD_PAGES_CNT * pageSize); 122 | } 123 | else 124 | free(stack); 125 | } 126 | } 127 | 128 | bool coro_stack::set(void *const p, const size_t s) 129 | { 130 | stack = p; 131 | size = s; 132 | #ifdef VALGRIND_SUPPORT 133 | valgrindId = VALGRIND_STACK_REGISTER(stack, size); 134 | #endif 135 | return true; 136 | } 137 | 138 | asm( 139 | ".text\n" 140 | ".globl coro_transfer\n" 141 | "coro_transfer:\n" 142 | 143 | #if __amd64 144 | "pushq %rbp\n" 145 | "pushq %rbx\n" 146 | "pushq %r12\n" 147 | "pushq %r13\n" 148 | "pushq %r14\n" 149 | "pushq %r15\n" 150 | "movq %rsp, (%rdi)\n" // store RSP in coro_ctx->sp (first function arg. stored in RDI) 151 | 152 | // http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/ 153 | // when coro_transfer() is invoked, the runtime makes sure that the IP is stored at RBP + 8 154 | // so when we resume here on in coro_resume, right after the popped 6 regs, we can expect 155 | // that we'll return to where the coro we switched to yielded when it _invoked_ either coro_transfer() or coro_resume() 156 | // (because when it called either of those, the IP at that point was set in the stack) 157 | // 158 | // coro_init_with_trampoline() is important because it setups the stack for new coros(not the 'main' context) 159 | // so that the 6 dummy regs are pushed in (we will pop them here and in coro_resume() impl.) 160 | // and that when coro_resume() or coro_transfer() returns the first time it's invoked for the coroutine, it will jmp to the trampoline 161 | 162 | "movq (%rsi), %rsp\n" // load RSP from coro_ctx->sp (second function arg. stored in RSI) 163 | "popq %r15\n" 164 | "popq %r14\n" 165 | "popq %r13\n" 166 | "popq %r12\n" 167 | "popq %rbx\n" 168 | "popq %rbp\n" 169 | 170 | #if 0 171 | // ret pops IP and jmps to it 172 | // we may as well just ret here directly 173 | "popq %rcx\n" 174 | "jmpq *%rcx\n" 175 | #else 176 | "ret\n" 177 | #endif 178 | 179 | ".globl coro_resume\n" 180 | "coro_resume:\n" 181 | "movq (%rdi), %rsp\n" 182 | "popq %r15\n" 183 | "popq %r14\n" 184 | "popq %r13\n" 185 | "popq %r12\n" 186 | "popq %rbx\n" 187 | "popq %rbp\n" 188 | 189 | "ret\n" 190 | 191 | #else 192 | #error arch not supported 193 | #endif 194 | ); 195 | 196 | void coro_init_with_trampoline(coro_ctx *ctx, void (*func)(void *), void *arg, coro_stack *const coroStack, void (*trampoline)()) 197 | { 198 | ctx->sp = (void **)(coroStack->size + (char *)coroStack->stack); 199 | ctx->sp = (void **)(uintptr_t(ctx->sp) & -16L); // 16-byte aligned down 200 | *--ctx->sp = (void *)abort; // in case trampoline returns, it will return to abort (trampoline should never return) 201 | 202 | *--ctx->sp = (void *)trampoline; 203 | // 6 regs tracked 204 | // coro_resume() will pop those 6 registers, and then it will jump to our trampoline (ret will pop the IP and jmp to it) 205 | ctx->sp -= 6; 206 | } 207 | 208 | #pragma mark Fibers 209 | void Fibers::FiberTrampoline() 210 | { 211 | #if __GCC_HAVE_DWARF2_CFI_ASM && __amd64 212 | asm(".cfi_undefined rip"); 213 | #endif 214 | 215 | auto &s = scheduler; 216 | 217 | s.cur->L(); 218 | s.on_fiber_exit(); 219 | 220 | assume_unreachable(); 221 | } 222 | 223 | Fibers::Scheduler::~Scheduler() 224 | { 225 | for (auto &it : freeStacks) 226 | it.release(); 227 | 228 | for (auto it : freeFibers) 229 | it->~fiber(); 230 | 231 | #ifdef HAVE_FIBER_PRIOS 232 | for (auto &it : ready) 233 | { 234 | while (it.next != &it) 235 | { 236 | auto f = switch_list_entry(fiber, list, it.next); 237 | 238 | switch_dlist_del(&f->list); 239 | f->stack.release(); 240 | f->~fiber(); 241 | } 242 | } 243 | #else 244 | while (ready.next != &read) 245 | { 246 | auto f = switch_list_entry(fiber, list, ready.next); 247 | 248 | switch_dlist_del(&f->list); 249 | f->stack.release(); 250 | f->~fiber(); 251 | } 252 | #endif 253 | } 254 | 255 | Fibers::fiber *Fibers::Scheduler::get_fiber() 256 | { 257 | auto *const f = freeFibers.size() ? freeFibers.Pop() : allocator.construct(); 258 | 259 | if (freeStacks.size()) 260 | f->stack = freeStacks.Pop(); 261 | else 262 | { 263 | f->stack.alloc(4096); 264 | // only need to initialize if its a new fiber 265 | // for reused fibers, we always switch_dlist_del_and_reset() on it when its scheduled anyway 266 | switch_dlist_init(&f->list); 267 | } 268 | 269 | ++aliveCnt; 270 | return f; 271 | } 272 | 273 | void Fibers::Scheduler::put_fiber(fiber *const f) 274 | { 275 | f->L = nullptr; 276 | 277 | freeFibers.push_back(f); 278 | if (freeStacks.size() > 32) 279 | { 280 | // Frugal 281 | f->stack.release(); 282 | } 283 | else 284 | freeStacks.push_back(f->stack); 285 | 286 | --aliveCnt; 287 | } 288 | 289 | void Fibers::Scheduler::make_ready(fiber *const f) 290 | { 291 | #ifdef HAVE_FIBER_PRIOS 292 | const auto prio = f->prio; 293 | 294 | switch_dlist_insert_before(&f->list, &ready[prio]); 295 | readyMask |= 1u << prio; 296 | #else 297 | switch_dlist_insert_before(&f->list, &ready); 298 | #endif 299 | 300 | ++readyCnt; 301 | } 302 | 303 | Fibers::fiber *Fibers::Scheduler::next_ready() 304 | { 305 | // O(1) scheduler 306 | #ifdef HAVE_FIBER_PRIOS 307 | const auto idx = SwitchBitOps::TrailingZeros(readyMask); 308 | auto &l = ready[idx]; 309 | #else 310 | auto &l = ready; 311 | #endif 312 | 313 | auto f = switch_list_entry(fiber, list, l.next); 314 | 315 | switch_dlist_del_and_reset(&f->list); 316 | 317 | #ifdef HAVE_FIBER_PRIOS 318 | if (switch_dlist_isempty(&l)) 319 | readyMask &= ~(1u << idx); 320 | #endif 321 | 322 | --readyCnt; 323 | return f; 324 | } 325 | 326 | void Fibers::Scheduler::resume(fiber *const f) 327 | { 328 | // The main scheduler may need to Resume() a bunch of suspended coros, and then just switch control to the next ready 329 | // or, maybe we 'd like to immediately transfer control here regardless of the priority. For now, we just 330 | // expect the scheduler implementation to YieldFromMain() at the end of the run loop iteration 331 | make_ready(f); 332 | } 333 | 334 | void Fibers::Scheduler::suspend() 335 | { 336 | #ifdef HAVE_FIBER_PRIOS 337 | if (!readyMask) 338 | #else 339 | if (!readyCnt) 340 | #endif 341 | { 342 | #ifdef HAVE_FIBER_TS 343 | cur->ts = Timings::Microseconds::Tick(); 344 | #endif 345 | 346 | ++ctxSwitches; 347 | coro_transfer(&cur->ctx, &mainCoro); 348 | } 349 | else 350 | { 351 | auto *const t = cur; 352 | 353 | cur = next_ready(); 354 | #ifdef HAVE_FIBER_TS 355 | cur->ts = t->ts = Timings::Microseconds::Tick(); 356 | 357 | #endif 358 | ++ctxSwitches; 359 | coro_transfer(&t->ctx, &cur->ctx); 360 | } 361 | } 362 | 363 | void Fibers::Scheduler::yield() 364 | { 365 | #ifdef HAVE_FIBER_PRIOS 366 | if (!readyMask) 367 | #else 368 | if (!readyCnt) 369 | #endif 370 | { 371 | // Just us! 372 | return; 373 | } 374 | else 375 | { 376 | auto *const t = cur; 377 | 378 | cur = next_ready(); 379 | 380 | #ifdef HAVE_FIBER_TS 381 | cur->ts = t->ts = Timings::Microseconds::Tick(); 382 | #endif 383 | 384 | make_ready(t); 385 | 386 | ++ctxSwitches; 387 | coro_transfer(&t->ctx, &cur->ctx); 388 | } 389 | } 390 | 391 | void Fibers::Scheduler::yield_from_main(coro_ctx *const ctx) 392 | { 393 | #ifdef HAVE_FIBER_PRIOS 394 | if (readyMask) 395 | #else 396 | if (readyCnt) 397 | #endif 398 | { 399 | cur = next_ready(); 400 | 401 | #ifdef HAVE_FIBER_TS 402 | cur->ts = Timings::Microseconds::Tick(); 403 | #endif 404 | 405 | ++ctxSwitches; 406 | coro_transfer(ctx, &cur->ctx); 407 | } 408 | } 409 | 410 | void Fibers::Scheduler::yield_from_main() 411 | { 412 | yield_from_main(&mainCoro); 413 | } 414 | 415 | void Fibers::Scheduler::await_fiber(fiber *const f) 416 | { 417 | auto *const prev = cur; 418 | 419 | f->awaits = cur; 420 | coro_init_with_trampoline(&f->ctx, nullptr, f, &f->stack, FiberTrampoline); 421 | 422 | if (!readyCnt) 423 | { 424 | // Fast-Path: no other ready coros, schedule this here 425 | cur = f; 426 | } 427 | else 428 | { 429 | make_ready(f); 430 | cur = next_ready(); 431 | } 432 | 433 | #ifdef HAVE_FIBER_TS 434 | prev->ts = cur->ts = Timings::Microseconds::Tick(); 435 | #endif 436 | 437 | ++ctxSwitches; 438 | coro_transfer(&prev->ctx, &cur->ctx); 439 | } 440 | 441 | void Fibers::Scheduler::on_fiber_exit() 442 | { 443 | if (auto *const f = cur->awaits) 444 | make_ready(f); 445 | 446 | // we 'll release cur coro, even though we could keep it around just so until 447 | // we coro_transfer() from it 448 | // we 'll just release and use coro_resume() directly to the next 449 | put_fiber(cur); 450 | 451 | ++ctxSwitches; 452 | #ifdef HAVE_FIBER_PRIOS 453 | if (!readyMask) 454 | #else 455 | if (!readyCnt) 456 | #endif 457 | { 458 | // Ok, switch back to main 459 | // Simple is beautiful 460 | coro_resume(&mainCoro); 461 | } 462 | 463 | cur = next_ready(); 464 | 465 | #ifdef HAVE_FIBER_TS 466 | cur->ts = Timings::Microseconds::Tick(); 467 | #endif 468 | 469 | coro_resume(&cur->ctx); 470 | } 471 | 472 | #ifdef EXAMPLE 473 | #pragma mark Example 474 | static uint32_t switchesCnt{0}; 475 | static coro_ctx ctx1, ctx2; 476 | uint64_t start; 477 | 478 | static void T1() 479 | { 480 | #if __GCC_HAVE_DWARF2_CFI_ASM && __amd64 481 | asm(".cfi_undefined rip"); 482 | #endif 483 | 484 | for (;;) 485 | { 486 | if (++switchesCnt == 20'000'000) 487 | { 488 | // About 58-62 million/second on a Xeon 2Ghz 489 | const auto duration = Timings::Microseconds::Since(start); 490 | 491 | Print("Took ", duration_repr(duration), " ", dotnotation_repr(1.0 / Timings::Microseconds::ToSecondsF(duration) * 20'000'000), "/sec\n"); 492 | _exit(1); 493 | } 494 | coro_transfer(&ctx1, &ctx2); 495 | } 496 | assume_unreachable(); 497 | } 498 | 499 | static void T2() 500 | { 501 | #if __GCC_HAVE_DWARF2_CFI_ASM && __amd64 502 | asm(".cfi_undefined rip"); 503 | #endif 504 | for (;;) 505 | { 506 | if (++switchesCnt == 20'000'000) 507 | { 508 | const auto duration = Timings::Microseconds::Since(start); 509 | 510 | Print("Took ", duration_repr(duration), " ", dotnotation_repr(1.0 / Timings::Microseconds::ToSecondsF(duration) * 20'000'000), "/sec\n"); 511 | _exit(1); 512 | } 513 | coro_transfer(&ctx2, &ctx1); 514 | } 515 | 516 | assume_unreachable(); 517 | } 518 | 519 | static constexpr bool trace{false}; 520 | int main(int argc, char *argv[]) 521 | { 522 | #if 0 523 | { 524 | uint32_t sum{0}; 525 | uint8_t mask = 1<<4; 526 | const auto b= Timings::Microseconds::Tick(); 527 | 528 | for (volatile uint32_t i{0}; i != 20'000'000; ++i) 529 | sum+=SwitchBitOps::TrailingZeros(mask); 530 | 531 | Print("Took ", duration_repr(Timings::Microseconds::Since(b)), "\n"); 532 | return 0; 533 | } 534 | #endif 535 | 536 | #if 0 537 | { 538 | // Benchmark context switching 539 | coro_stack s1, s2; 540 | 541 | s1.Alloc(4096); 542 | s2.Alloc(4096); 543 | 544 | start = Timings::Microseconds::Tick(); 545 | coro_init_with_trampoline(&ctx1, nullptr, nullptr, &s1, T1); 546 | coro_init_with_trampoline(&ctx2, nullptr, nullptr, &s2, T2); 547 | 548 | coro_resume(&ctx1); 549 | 550 | exit(0); 551 | } 552 | #endif 553 | 554 | const auto b = Timings::Microseconds::Tick(); 555 | 556 | Fibers::scheduler.schedule([]() { 557 | Fibers::scheduler.schedule([]() { 558 | for (uint32_t i{0}; i != 65536 / 2; ++i) 559 | { 560 | if (trace) 561 | Print("NOW at ", ansifmt::color_green, ptr_repr(Fibers::scheduler.cur), ansifmt::reset, "\n"); 562 | 563 | Fibers::scheduler.yield(); 564 | } 565 | }); 566 | 567 | for (uint32_t i{0}; i != 65536 / 2; ++i) 568 | { 569 | if (trace) 570 | Print("NOW at ", ansifmt::color_brown, ptr_repr(Fibers::scheduler.cur), ansifmt::reset, "\n"); 571 | 572 | Fibers::scheduler.yield(); 573 | } 574 | 575 | }); 576 | 577 | Fibers::scheduler.yield_from_main(); 578 | 579 | Print("Took ", duration_repr(Timings::Microseconds::Since(b)), " ", dotnotation_repr(Fibers::scheduler.ctxSwitches), "\n"); 580 | 581 | if (trace) 582 | Print("Now back to main\n"); 583 | 584 | return 0; 585 | } 586 | #endif 587 | -------------------------------------------------------------------------------- /Switch/buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef __linux__ 3 | #include 4 | #endif 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class Buffer 12 | { 13 | public: 14 | using iterator = char *; 15 | using const_iterator = const char *; 16 | using reference = char &; 17 | using const_reference = const char &; 18 | using value_type = char; 19 | 20 | static constexpr uint32_t npos = UINT32_MAX; 21 | 22 | public: // std::string API 23 | iterator begin() noexcept 24 | { 25 | return buffer; 26 | } 27 | 28 | const_iterator cbegin() const noexcept 29 | { 30 | return buffer; 31 | } 32 | 33 | iterator end() noexcept 34 | { 35 | return buffer + length_; 36 | } 37 | 38 | const_iterator end() const noexcept 39 | { 40 | return buffer + length_; 41 | } 42 | 43 | uint32_t size() const noexcept 44 | { 45 | return length_; 46 | } 47 | 48 | uint32_t max_size() const noexcept 49 | { 50 | return UINT32_MAX - 1; 51 | } 52 | 53 | void resize(const uint32_t n) 54 | { 55 | if (unlikely(n < max_size())) 56 | SetLength(n); 57 | else 58 | EnsureCapacity(n); 59 | } 60 | 61 | uint32_t capacity() const noexcept 62 | { 63 | return max_size(); 64 | } 65 | 66 | void reserve(const uint32_t n) 67 | { 68 | EnsureCapacity(n + 1); 69 | } 70 | 71 | void clear() noexcept 72 | { 73 | length_ = 0; 74 | } 75 | 76 | bool empty() const noexcept 77 | { 78 | return !length_; 79 | } 80 | 81 | void shrink_to_fit() 82 | { 83 | // NO-OP for now 84 | } 85 | 86 | char &at(const uint32_t pos) 87 | { 88 | return buffer[pos]; 89 | } 90 | 91 | const char &at(const uint32_t pos) const 92 | { 93 | return buffer[pos]; 94 | } 95 | 96 | char &back() 97 | { 98 | return buffer[length_ - 1]; 99 | } 100 | 101 | const char &back() const 102 | { 103 | return buffer[length_ - 1]; 104 | } 105 | 106 | char &front() 107 | { 108 | return *buffer; 109 | } 110 | 111 | const char &front() const 112 | { 113 | return *buffer; 114 | } 115 | 116 | inline Buffer &operator+=(const char c) 117 | { 118 | Append(c); 119 | return *this; 120 | } 121 | 122 | inline Buffer &operator+=(const char *const str) 123 | { 124 | Append(str); 125 | return *this; 126 | } 127 | 128 | inline Buffer &operator+=(std::initializer_list il) 129 | { 130 | EnsureCapacity(il.size()); 131 | for (const auto it : il) 132 | Append(it); 133 | return *this; 134 | } 135 | 136 | void push_back(const char c) 137 | { 138 | Append(c); 139 | } 140 | 141 | Buffer &erase(const uint32_t pos = 0, const uint32_t len = npos) 142 | { 143 | DeleteChunk(pos, len != npos ? len : length_ - pos); 144 | return *this; 145 | } 146 | 147 | Buffer &erase(const_iterator p) 148 | { 149 | DeleteChunk(p - buffer, 1); 150 | return *this; 151 | } 152 | 153 | Buffer &erase(const_iterator first, const_iterator last) 154 | { 155 | DeleteChunk(first - buffer, last - first); 156 | return *this; 157 | } 158 | 159 | void pop_back() 160 | { 161 | AdjustLength(1); 162 | } 163 | 164 | const char *c_str() const noexcept 165 | { 166 | return buffer; 167 | } 168 | 169 | public: 170 | [[gnu::always_inline]] inline uint32_t Reserved() const 171 | { 172 | #ifdef SWITCH_HAVE_MALLOC_USABLE_SIZE 173 | return likely(buffer) ? malloc_usable_size(buffer) : 0; 174 | #else 175 | return reserved_; 176 | #endif 177 | } 178 | 179 | [[gnu::always_inline]] inline auto length() const 180 | { 181 | return length_; 182 | } 183 | 184 | [[gnu::always_inline]] inline auto data() const 185 | { 186 | return buffer; 187 | } 188 | 189 | inline strwlen8_t AsS8() const 190 | { 191 | return strwlen8_t(buffer, length_); 192 | } 193 | 194 | inline strwlen16_t AsS16() const 195 | { 196 | return strwlen16_t(buffer, length_); 197 | } 198 | 199 | inline strwlen32_t AsS32() const 200 | { 201 | return strwlen32_t(buffer, length_); 202 | } 203 | 204 | inline void FreeBuf() 205 | { 206 | WillUpdate(); 207 | if (likely(buffer)) 208 | { 209 | ::free(buffer); 210 | buffer = nullptr; 211 | SetReserved(0); 212 | length_ = 0; 213 | } 214 | } 215 | 216 | [[gnu::always_inline]] inline bool IsNullTerminated() const 217 | { 218 | return buffer && buffer[length_] == '\0'; 219 | } 220 | 221 | [[gnu::always_inline]] inline operator char *() 222 | { 223 | return buffer ?: const_cast(""); // Too many uses so we need to do it 224 | } 225 | 226 | [[gnu::always_inline]] inline bool operator==(const char *const data) const 227 | { 228 | if (buffer == data && data == nullptr) 229 | return true; 230 | else if (likely(buffer)) 231 | return AsS32().Eq(data, strlen(data)); 232 | else 233 | return false; 234 | } 235 | 236 | auto operator!=(const Buffer &o) const 237 | { 238 | return AsS32() != o.AsS32(); 239 | } 240 | 241 | [[gnu::always_inline]] inline bool operator!=(const char *const data) const 242 | { 243 | return !(operator==(data)); 244 | } 245 | 246 | [[gnu::always_inline]] inline bool operator==(const Buffer &other) const 247 | { 248 | return length_ == other.length_ ? !memcmp(buffer, other.buffer, length_) : false; 249 | } 250 | 251 | inline Buffer &operator=(const char *const data) 252 | { 253 | Flush(); 254 | Append(data); 255 | 256 | return *this; 257 | } 258 | 259 | inline Buffer &operator=(const Buffer &other) 260 | { 261 | Flush(); 262 | Append(other.data(), other.length()); 263 | 264 | return *this; 265 | } 266 | 267 | inline Buffer &operator+(const char *const data) 268 | { 269 | 270 | Append(data); 271 | return *this; 272 | } 273 | 274 | inline void Append(const strwlen8_t *const s) 275 | { 276 | Append(s->p, s->len); 277 | } 278 | 279 | inline void Append(const strwlen16_t *const s) 280 | { 281 | Append(s->p, s->len); 282 | } 283 | 284 | inline void Append(const strwlen32_t *const s) 285 | { 286 | Append(s->p, s->len); 287 | } 288 | 289 | inline void Append(const strwlen8_t &s) 290 | { 291 | Append(s.p, s.len); 292 | } 293 | 294 | inline void Append(const strwlen16_t &s) 295 | { 296 | Append(s.p, s.len); 297 | } 298 | 299 | inline void Append(const strwlen32_t &s) 300 | { 301 | Append(s.p, s.len); 302 | } 303 | 304 | inline Buffer &operator+(const Buffer &other) 305 | { 306 | Append(other.data(), other.length()); 307 | return *this; 308 | } 309 | 310 | inline Buffer &operator+=(const Buffer &other) 311 | { 312 | Append(other.data(), other.length()); 313 | return *this; 314 | } 315 | 316 | [[gnu::always_inline]] inline char *At(const uint32_t offset) const 317 | { 318 | WillReference(offset); 319 | 320 | return buffer + offset; 321 | } 322 | 323 | [[gnu::always_inline]] inline char *End() const 324 | { 325 | return buffer + length_; 326 | } 327 | 328 | [[gnu::always_inline]] inline uint32_t OffsetOf(const char *const ptr) 329 | { 330 | return ptr - buffer; 331 | } 332 | 333 | inline uint8_t LastChar() const 334 | { 335 | return likely(length_) ? buffer[length_ - 1] : 0; 336 | } 337 | 338 | inline char FirstChar() const 339 | { 340 | return likely(length_) ? *buffer : 0; 341 | } 342 | 343 | inline char operator[](const uint32_t index) const 344 | { 345 | if (likely(index <= length_)) 346 | return buffer[index]; 347 | 348 | abort(); 349 | return 0; 350 | } 351 | 352 | /** Releases the internal buffer, and resets the structure, causing the internal buffer to 353 | * be reallocated on further requests . Useful in some scenarios where we need to 354 | * get for oursleves the internal buffer 355 | */ 356 | 357 | inline void ReleaseBuffer() 358 | { 359 | buffer = nullptr; 360 | SetReserved(0); 361 | length_ = 0; 362 | } 363 | 364 | inline char *GetAndReleaseBuffer() 365 | { 366 | char *const b = buffer; 367 | 368 | buffer = nullptr; 369 | SetReserved(0); 370 | length_ = 0; 371 | 372 | return b; 373 | } 374 | 375 | inline void FreeResources() 376 | { 377 | FreeBuf(); 378 | length_ = 0; 379 | } 380 | 381 | void Exchange(Buffer *other); 382 | 383 | int32_t IndexOf(const char *const needle); 384 | 385 | uint32_t CountOf(const char c) const 386 | { 387 | return strwlen32_t(buffer, length_).CountOf(c); 388 | } 389 | 390 | int32_t Replace(const strwlen32_t from, const strwlen32_t to); 391 | 392 | uint32_t Replace(const char needle, const char with); 393 | 394 | Buffer() 395 | : buffer{nullptr}, length_{0} 396 | { 397 | } 398 | 399 | Buffer(Buffer &&o) 400 | { 401 | length_ = o.length_; 402 | SetReserved(o.Reserved()); 403 | 404 | buffer = o.buffer; 405 | SetOwnsBuffer(o.OwnsBuffer()); 406 | 407 | o.length_ = 0; 408 | o.SetReserved(0); 409 | o.buffer = nullptr; 410 | o.SetOwnsBuffer(false); 411 | } 412 | 413 | Buffer &operator=(Buffer &&o) 414 | { 415 | if (buffer && OwnsBuffer()) 416 | free(buffer); 417 | 418 | length_ = o.length_; 419 | SetReserved(o.Reserved()); 420 | 421 | buffer = o.buffer; 422 | SetOwnsBuffer(o.OwnsBuffer()); 423 | 424 | o.length_ = 0; 425 | o.SetReserved(0); 426 | o.buffer = nullptr; 427 | o.SetOwnsBuffer(false); 428 | 429 | return *this; 430 | } 431 | 432 | Buffer(const uint32_t initSize); 433 | 434 | Buffer(const char *const p, const uint32_t l); 435 | 436 | Buffer(const strwlen32_t content) 437 | : Buffer(content.p, content.len) 438 | { 439 | } 440 | 441 | Buffer(const strwlen16_t content) 442 | : Buffer(content.p, content.len) 443 | { 444 | } 445 | 446 | Buffer(const strwlen8_t content) 447 | : Buffer(content.p, content.len) 448 | { 449 | } 450 | 451 | Buffer(const Buffer &other) 452 | : Buffer(other.data(), other.length()) 453 | { 454 | } 455 | 456 | virtual ~Buffer() 457 | { 458 | if (likely(OwnsBuffer()) && buffer) 459 | free(buffer); 460 | } 461 | 462 | void AppendFmt(const char *fmt, ...) 463 | __attribute__((format(printf, 2, 3))) 464 | { 465 | WillUpdate(); 466 | 467 | va_list args; 468 | const int32_t capacity = Capacity(); 469 | 470 | va_start(args, fmt); 471 | const auto len = vsnprintf(buffer + length_, capacity, fmt, args); 472 | 473 | if (unlikely(len < 0)) 474 | { 475 | (void)va_end(args); 476 | return; 477 | } 478 | else if (len >= capacity) 479 | { 480 | EnsureCapacity(len + 1); 481 | 482 | va_end(args); 483 | va_start(args, fmt); 484 | 485 | const auto r = vsnprintf(buffer + length_, len + 1, fmt, args); 486 | 487 | if (unlikely(r < 0)) 488 | { 489 | va_end(args); 490 | return; 491 | } 492 | } 493 | va_end(args); 494 | 495 | length_ += len; 496 | buffer[length_] = '\0'; 497 | } 498 | 499 | static inline uint32_t ComputeNewSize(const uint32_t requestedSize) 500 | { 501 | if (requestedSize < 0x4000) 502 | return ((requestedSize + (requestedSize >> 3)) | 15) + 1; 503 | else if (requestedSize < 0x80000) 504 | return ((requestedSize + requestedSize) | 0xfff) + 1; 505 | else 506 | return ((requestedSize + (requestedSize >> 3)) | 0xfff) + 1; 507 | } 508 | 509 | #pragma mark Will/Did delegates 510 | [[gnu::always_inline]] inline void WillTouch(const uint32_t o, const uint32_t l) const 511 | { 512 | //expect(o + l < Reserved()); 513 | (void)o; 514 | (void)l; 515 | } 516 | 517 | [[gnu::always_inline]] inline void WillSetLength(const uint32_t l) const { 518 | //expect_reason(OwnsBuffer() && length_ < Reserved(), "Unexpected"); 519 | } 520 | 521 | [[gnu::always_inline]] inline void WillReference(const uint32_t offset) const { 522 | //expect_reason(offset == 0 || (likely(OwnsBuffer()) && offset < Reserved()), "Unexpected DataWithOffset()"); 523 | } 524 | 525 | [[gnu::always_inline]] inline void WillUpdate() 526 | { 527 | // Enable this if you want to track down an issue; otherwise it's too expensive, and, it makes using this pattern an issue 528 | // expect_reason(OwnsBuffer(), "Attempting to update an immutable Buffer"); 529 | } 530 | 531 | void EnsureSize(const uint32_t newMin) 532 | { 533 | const uint32_t r = Reserved(); 534 | 535 | WillUpdate(); 536 | 537 | if (unlikely(newMin > r)) 538 | { 539 | uint32_t newSize = ComputeNewSize(newMin); 540 | 541 | try 542 | { 543 | if (buffer == nullptr || unlikely(!OwnsBuffer())) 544 | buffer = (char *)malloc(sizeof(char) * newSize); 545 | else if (length_ == 0) 546 | { 547 | ::free(buffer); 548 | buffer = (char *)malloc(sizeof(char) * newSize); 549 | } 550 | else 551 | { 552 | buffer = (char *)realloc(buffer, newSize); 553 | } 554 | } 555 | catch (...) 556 | { 557 | std::abort(); 558 | } 559 | 560 | if (unlikely(!buffer)) 561 | std::abort(); 562 | 563 | #ifdef SWITCH_HAVE_MALLOC_USABLE_SIZE 564 | newSize = malloc_usable_size(buffer); 565 | #endif 566 | 567 | SetReserved(newSize); 568 | SetOwnsBuffer(true); 569 | 570 | buffer[length_] = '\0'; 571 | } 572 | } 573 | 574 | [[gnu::always_inline]] inline void EnsureCapacity(const uint32_t n) 575 | { 576 | EnsureSize(length_ + n + 1); // +1 for trailing \0 577 | } 578 | 579 | [[gnu::always_inline]] inline void WillInsert(const uint32_t n) 580 | { 581 | EnsureCapacity(n); 582 | } 583 | 584 | inline void Append(const char *const data, const uint32_t len) 585 | { 586 | WillInsert(len); 587 | memcpy(buffer + length_, data, len); 588 | length_ += len; 589 | buffer[length_] = '\0'; 590 | } 591 | 592 | inline void Append(const char *const data) 593 | { 594 | Append(data, strlen(data)); 595 | } 596 | 597 | inline void Append(const Buffer &buf) 598 | { 599 | Append(buf.data(), buf.length()); 600 | } 601 | 602 | void Append(const char c, const uint32_t cnt); 603 | 604 | void Append(const char c) 605 | { 606 | WillInsert(1); 607 | buffer[length_++] = c; 608 | buffer[length_] = '\0'; 609 | } 610 | 611 | void Append(const uint8_t c) 612 | { 613 | WillInsert(1); 614 | 615 | buffer[length_++] = c; 616 | buffer[length_] = '\0'; 617 | } 618 | 619 | void Append(const float f) 620 | { 621 | AppendFmt("%lf", f); 622 | } 623 | 624 | void Append(const int i) 625 | { 626 | AppendFmt("%i", i); 627 | } 628 | 629 | void Append(const uint32_t i) 630 | { 631 | AppendFmt("%u", i); 632 | } 633 | 634 | [[gnu::always_inline]] inline void SetLengthAndTerm(const uint32_t l) 635 | { 636 | length_ = l; 637 | 638 | WillSetLength(length_); 639 | buffer[length_] = '\0'; 640 | } 641 | 642 | inline void SetLength(const uint32_t newLength) 643 | { 644 | WillSetLength(newLength); 645 | 646 | Drequire(newLength < Reserved() && buffer); 647 | 648 | length_ = newLength; 649 | buffer[length_] = '\0'; 650 | } 651 | 652 | void TrimWS(); 653 | 654 | void TrimWSAll(); 655 | 656 | inline bool IsBlank() const 657 | { 658 | return strwlen32_t(buffer, length_).IsBlank(); 659 | } 660 | 661 | inline void AdjustLength(const uint32_t factor) 662 | { 663 | assert(length_ >= factor); 664 | 665 | length_ -= factor; 666 | *(buffer + length_) = '\0'; 667 | } 668 | 669 | inline void ReplaceLastCharWith(const char newChar) 670 | { 671 | if (length_) 672 | buffer[length_ - 1] = newChar; 673 | } 674 | 675 | inline void AdvanceLength(const uint32_t factor) 676 | { 677 | assert(length_ + factor < Reserved()); 678 | assert(OwnsBuffer()); 679 | 680 | length_ += factor; 681 | buffer[length_] = '\0'; 682 | } 683 | 684 | void DeleteChunk(const uint32_t pos, uint32_t gapLength) 685 | { 686 | WillUpdate(); 687 | 688 | if (gapLength + pos > length_) 689 | gapLength = length_ - pos; 690 | 691 | if (gapLength > 0) 692 | { 693 | memmove(buffer + pos, buffer + pos + gapLength, length_ - pos - gapLength); 694 | length_ -= gapLength; 695 | } 696 | } 697 | 698 | // Includes space reserved for \0 699 | [[gnu::always_inline]] inline uint32_t ActualCapacity() const 700 | { 701 | Drequire(OwnsBuffer()); 702 | return Reserved() - length_; 703 | } 704 | 705 | [[gnu::always_inline]] inline uint32_t Capacity() const 706 | { 707 | const uint32_t r = Reserved(); 708 | 709 | return r > length_ ? (r - length_) - 1 : 0; 710 | } 711 | 712 | inline void SetData(char *const d) 713 | { 714 | buffer = d; 715 | } 716 | 717 | inline void Reset() 718 | { 719 | if (length_) 720 | { 721 | if (likely(buffer)) 722 | *buffer = '\0'; 723 | 724 | length_ = 0; 725 | } 726 | } 727 | 728 | inline void Flush() 729 | { 730 | Reset(); 731 | } 732 | 733 | [[gnu::always_inline]] inline void SetZeroLength() 734 | { 735 | length_ = 0; 736 | } 737 | 738 | inline void PushIntUnsafe(const int v) 739 | { 740 | WillUpdate(); 741 | 742 | *(int *)(buffer + length_) = v; 743 | length_ += sizeof(int); 744 | } 745 | 746 | inline uint32_t AsUint32() const 747 | { 748 | return strwlen32_t(buffer, length_).AsUint32(); 749 | } 750 | 751 | uint64_t AsUint64() const 752 | { 753 | return strwlen32_t(buffer, length_).AsUint64(); 754 | } 755 | 756 | inline int32_t AsInt32() const 757 | { 758 | return strwlen32_t(buffer, length_).AsInt32(); 759 | } 760 | 761 | bool IsAllDigits() const 762 | { 763 | for (const char *p = buffer, *const e = p + length_; p != e; ++p) 764 | { 765 | if (!isdigit(*p)) 766 | return false; 767 | } 768 | return true; 769 | } 770 | 771 | inline void PushIntsPairUnsafe(const int v1, const int v2) 772 | { 773 | WillUpdate(); 774 | 775 | *(int *)(buffer + length_) = v1; 776 | length_ += sizeof(int); 777 | *(int *)(buffer + length_) = v2; 778 | length_ += sizeof(int); 779 | } 780 | void SetDataAndLength(char *const d, const uint32_t l) 781 | { 782 | // Make sure you know what you are doing 783 | buffer = d; 784 | length_ = l; 785 | } 786 | 787 | void SetDataAndSize(char *const d, const uint32_t s) 788 | { 789 | buffer = d; 790 | length_ = 0; 791 | SetReserved(s); 792 | } 793 | 794 | void *MakeSpace(const uint32_t s) 795 | { 796 | void *ptr; 797 | 798 | WillInsert(s); 799 | ptr = buffer + length_; 800 | 801 | if (likely(s)) 802 | { 803 | length_ += s; 804 | buffer[length_] = '\0'; 805 | } 806 | 807 | return ptr; 808 | } 809 | 810 | void PadUptoWith(uint32_t n, const char c) 811 | { 812 | WillUpdate(); 813 | 814 | if (n <= length_) 815 | return; 816 | 817 | EnsureCapacity(n - length_ + 8); 818 | memset(buffer + length_, c, n - length_); 819 | length_ = n; 820 | buffer[length_] = '\0'; 821 | } 822 | 823 | public: 824 | template 825 | static Buffer build(Arg &&... args) 826 | { 827 | Buffer b; 828 | 829 | ToBuffer(b, std::forward(args)...); 830 | return b; 831 | } 832 | 833 | template 834 | auto &append(Arg &&... args) 835 | { 836 | ToBuffer(*this, std::forward(args)...); 837 | return *this; 838 | } 839 | 840 | inline auto &append(const Buffer &str) 841 | { 842 | Append(str); 843 | return *this; 844 | } 845 | 846 | inline auto &append(const Buffer &str, const uint32_t pos, const uint32_t len) 847 | { 848 | Append(str.buffer + pos, len); 849 | return *this; 850 | } 851 | 852 | inline auto &append(const uint32_t n, const char c) 853 | { 854 | EnsureCapacity(n); 855 | 856 | for (uint32_t i{0}; i != n; ++i) 857 | buffer[length_ + i] = c; 858 | 859 | length_ += n; 860 | buffer[length_] = '\0'; 861 | return *this; 862 | } 863 | 864 | inline auto &append(std::initializer_list il) 865 | { 866 | EnsureCapacity(il.size()); 867 | for (const auto it : il) 868 | Append(it); 869 | 870 | return *this; 871 | } 872 | 873 | [[gnu::always_inline]] inline virtual bool OwnsBuffer() const 874 | { 875 | return true; 876 | } 877 | 878 | protected: 879 | char *buffer; 880 | uint32_t length_; 881 | #ifndef SWITCH_HAVE_MALLOC_USABLE_SIZE 882 | uint32_t reserved_{0}; 883 | #endif 884 | 885 | [[gnu::always_inline]] inline void SetOwnsBuffer(const bool v) 886 | { 887 | (void)v; // NO-OP for now 888 | } 889 | 890 | inline void SetReserved(const uint32_t newSize) 891 | { 892 | #ifndef SWITCH_HAVE_MALLOC_USABLE_SIZE 893 | reserved_ = newSize; 894 | #else 895 | (void)newSize; 896 | #endif 897 | } 898 | }; 899 | 900 | class IOBuffer 901 | : public Buffer 902 | { 903 | private: 904 | uint32_t position; 905 | 906 | private: 907 | static const uint32_t sizeOf; 908 | 909 | public: 910 | // Specialized for IOBuffer() 911 | // Buffer::EnsureCapacity() asks for length_ + n + 1 because 912 | // it needs to set the trailing \0 913 | // 914 | // UPDATE: we _need_ +1 for \0, otherwise Buffer::SetLength() fails because 915 | // newLen == Reserved(), if e.g we Reserve(8) and newLen == 8 916 | [[gnu::always_inline]] inline void WillInsert(const uint32_t n) 917 | { 918 | EnsureSize(length_ + n); // no need to +1 for trailing \0 919 | } 920 | 921 | void SetLengthAndTerm(const uint32_t l) 922 | { 923 | Drequire(l + 1 <= Reserved()); 924 | 925 | Buffer::SetLengthAndTerm(l); 926 | } 927 | 928 | void SetLength(const uint32_t nl) 929 | { 930 | Drequire(nl <= Reserved()); 931 | 932 | length_ = nl; 933 | } 934 | 935 | void makeNullTerminated() 936 | { 937 | if (length_ < Reserved()) 938 | { 939 | buffer[length_] = '\0'; 940 | return; 941 | } 942 | else 943 | { 944 | WillInsert(1); 945 | buffer[length_] = '\0'; 946 | } 947 | } 948 | 949 | uint8_t *RoomFor(const size_t s) 950 | { 951 | WillInsert(s); 952 | 953 | auto *const ptr = buffer + length_; 954 | 955 | length_ += s; 956 | 957 | return (uint8_t *)ptr; 958 | } 959 | 960 | void DumpStrings() const; 961 | 962 | [[gnu::always_inline]] inline char *AtOffset() const 963 | { 964 | return buffer + position; 965 | } 966 | 967 | void FreeBuf() 968 | { 969 | Buffer::FreeBuf(); 970 | position = 0; 971 | } 972 | 973 | void SetDataAndLength(char *const d, const uint32_t l) 974 | { 975 | buffer = d; 976 | length_ = l; 977 | position = 0; 978 | } 979 | 980 | IOBuffer() 981 | : position{0} 982 | { 983 | } 984 | 985 | IOBuffer(const uint32_t len) 986 | : Buffer{len}, position{0} 987 | { 988 | } 989 | 990 | IOBuffer(const char *const p, const uint32_t len) 991 | : Buffer{p, len}, position{0} 992 | { 993 | } 994 | 995 | IOBuffer(IOBuffer &&b) 996 | : Buffer(std::move(b)), position{b.position} 997 | { 998 | b.ReleaseBuffer(); 999 | b.SetOffset(uint64_t(0)); 1000 | } 1001 | 1002 | IOBuffer(const IOBuffer &b) 1003 | : Buffer(b.buffer, b.length_), position{b.position} 1004 | { 1005 | } 1006 | 1007 | auto &operator=(const IOBuffer &b) 1008 | { 1009 | position = b.position; 1010 | length_ = 0; 1011 | Serialize(b.buffer, b.length_); 1012 | 1013 | return *this; 1014 | } 1015 | 1016 | auto &operator=(IOBuffer &&o) 1017 | { 1018 | FreeBuf(); 1019 | 1020 | buffer = o.buffer; 1021 | length_ = o.length_; 1022 | position = o.position; 1023 | SetReserved(o.Reserved()); 1024 | 1025 | o.ReleaseBuffer(); 1026 | o.position = 0; 1027 | 1028 | return *this; 1029 | } 1030 | 1031 | inline void Reset() 1032 | { 1033 | position = 0; 1034 | Buffer::Reset(); 1035 | } 1036 | 1037 | inline void QuickFlush() 1038 | { 1039 | length_ = 0; 1040 | position = 0; 1041 | } 1042 | 1043 | inline void Flush() 1044 | { 1045 | Buffer::Flush(); 1046 | position = 0; 1047 | } 1048 | 1049 | void clear() 1050 | { 1051 | Buffer::Flush(); 1052 | position = 0; 1053 | } 1054 | 1055 | auto Offset() const 1056 | { 1057 | return position; 1058 | } 1059 | 1060 | int ReadFromSocket(const int fd, const uint32_t chunkSize = 8192); 1061 | 1062 | int ReadFromFile(const int fd, off64_t offset = 0, int32_t bytesToRead = -1); 1063 | 1064 | int ReadFromFile(FILE *fh); 1065 | 1066 | int ReadFromFile(const char *const path, const off64_t offset = 0, const int32_t bytesToRead = -1); 1067 | 1068 | int SaveInFile(const char *const path, const off64_t offset = 0, const int32_t bytesToWrite = -1) const; 1069 | 1070 | int SaveInFile(int fd, const off64_t offset = 0, int32_t bytesToWrite = -1) const; 1071 | 1072 | int SaveInFileWithPermissions(const char *const, const mode_t mode) const; 1073 | 1074 | int WriteToSocket(const int fd); 1075 | 1076 | int WriteRangeToSocket(const int fd, uint32_t &bytesLeft); 1077 | 1078 | inline void SetPosition(const uint32_t pos) 1079 | { 1080 | position = pos; 1081 | } 1082 | 1083 | inline void ResetPosition() 1084 | { 1085 | position = 0; 1086 | } 1087 | 1088 | inline void AdvancePosition(const uint32_t pos) 1089 | { 1090 | position += pos; 1091 | } 1092 | 1093 | inline void SetOffset(const uint64_t o) 1094 | { 1095 | position = (uint32_t)o; 1096 | } 1097 | 1098 | inline void SetOffset(const char *const p) 1099 | { 1100 | position = p - buffer; 1101 | } 1102 | 1103 | inline uint64_t GetOffset() const 1104 | { 1105 | return position; 1106 | } 1107 | 1108 | inline void AdvanceOffset(const uint64_t s) 1109 | { 1110 | position += s; 1111 | } 1112 | 1113 | void AdvanceOffsetTo(const char *const p) 1114 | { 1115 | position = p - buffer; 1116 | } 1117 | 1118 | void ShiftOffset(const int32_t by) 1119 | { 1120 | position += by; 1121 | } 1122 | 1123 | void AdjustOffsetAndLength(const uint32_t n) 1124 | { 1125 | position += n; 1126 | length_ -= n; 1127 | } 1128 | 1129 | inline uint32_t Position() const 1130 | { 1131 | return position; 1132 | } 1133 | 1134 | [[gnu::always_inline]] inline bool IsPositionAtEnd() const 1135 | { 1136 | return position == length_; 1137 | } 1138 | 1139 | inline operator char *() 1140 | { 1141 | return data() ?: const_cast(""); 1142 | } 1143 | 1144 | inline Buffer &operator=(const char *const data) 1145 | { 1146 | Buffer::operator=(data); 1147 | this->position = 0; 1148 | 1149 | return *this; 1150 | } 1151 | 1152 | Buffer &operator=(const strwlen32_t content) 1153 | { 1154 | length_ = 0; 1155 | Append(content); 1156 | return *this; 1157 | } 1158 | 1159 | Buffer &operator=(const strwlen16_t content) 1160 | { 1161 | length_ = 0; 1162 | Append(content); 1163 | return *this; 1164 | } 1165 | 1166 | Buffer &operator=(const strwlen8_t content) 1167 | { 1168 | length_ = 0; 1169 | Append(content); 1170 | return *this; 1171 | } 1172 | 1173 | inline bool RetrieveBinarySafe(void *const d, const uint32_t d_size) 1174 | { 1175 | memcpy(d, buffer + position, d_size); 1176 | position += d_size; 1177 | 1178 | return true; 1179 | } 1180 | 1181 | void SerializeVarUInt32(const uint32_t n); 1182 | 1183 | inline uint32_t UnserializeVarUInt32(void); 1184 | 1185 | [[gnu::always_inline]] inline void Serialize(const uint8_t v) 1186 | { 1187 | *(typename std::remove_const::type *)RoomFor(sizeof(v)) = v; 1188 | } 1189 | 1190 | [[gnu::always_inline]] inline void Serialize(const uint16_t v) 1191 | { 1192 | *(typename std::remove_const::type *)RoomFor(sizeof(v)) = v; 1193 | } 1194 | 1195 | [[gnu::always_inline]] inline void Serialize(const uint32_t v) 1196 | { 1197 | *(typename std::remove_const::type *)RoomFor(sizeof(v)) = v; 1198 | } 1199 | 1200 | [[gnu::always_inline]] inline void Serialize(const uint64_t v) 1201 | { 1202 | *(typename std::remove_const::type *)RoomFor(sizeof(v)) = v; 1203 | } 1204 | 1205 | [[gnu::always_inline]] inline void Serialize(const float v) 1206 | { 1207 | *(typename std::remove_const::type *)RoomFor(sizeof(v)) = v; 1208 | } 1209 | 1210 | [[gnu::always_inline]] inline void Serialize(const double v) 1211 | { 1212 | *(typename std::remove_const::type *)RoomFor(sizeof(v)) = v; 1213 | } 1214 | 1215 | inline void Serialize(const strwlen8_t s) 1216 | { 1217 | Serialize(s.len); 1218 | Serialize(s.p, s.len); 1219 | } 1220 | 1221 | inline void Serialize(const strwlen16_t s) 1222 | { 1223 | SerializeVarUInt32(s.len); 1224 | Serialize(s.p, s.len); 1225 | } 1226 | 1227 | inline void Serialize(const strwlen32_t s) 1228 | { 1229 | SerializeVarUInt32(s.len); 1230 | Serialize(s.p, s.len); 1231 | } 1232 | 1233 | template 1234 | [[gnu::always_inline]] inline void Serialize(const T v) 1235 | { 1236 | memcpy(RoomFor(sizeof(T)), &v, sizeof(v)); 1237 | } 1238 | 1239 | template 1240 | [[gnu::always_inline]] inline void Serialize(const T *v) 1241 | { 1242 | memcpy(RoomFor(sizeof(T)), v, sizeof(T)); 1243 | } 1244 | 1245 | [[gnu::always_inline]] inline void Serialize(const void *const p, const uint32_t size) 1246 | { 1247 | memcpy(RoomFor(size), p, size); 1248 | } 1249 | 1250 | [[gnu::always_inline]] inline void *Peek(const uint32_t size) 1251 | { 1252 | return buffer + position; 1253 | } 1254 | 1255 | [[gnu::always_inline]] inline bool IsAtEnd() const 1256 | { 1257 | return position == length_; 1258 | } 1259 | 1260 | template 1261 | [[gnu::always_inline]] inline T Unserialize() 1262 | { 1263 | const T r = *(T *)(buffer + position); 1264 | 1265 | position += sizeof(T); 1266 | return r; 1267 | } 1268 | 1269 | template 1270 | [[gnu::always_inline]] inline void Unserialize(T *const dst) 1271 | { 1272 | memcpy(dst, buffer + position, sizeof(T)); 1273 | position += sizeof(T); 1274 | } 1275 | 1276 | template 1277 | [[gnu::always_inline]] inline bool UnserializeSafe(T *const dst) 1278 | { 1279 | return RetrieveBinarySafe(dst, sizeof(T)); 1280 | } 1281 | 1282 | [[gnu::always_inline]] inline void Unserialize(void *const p, const uint32_t size) 1283 | { 1284 | memcpy(p, buffer + position, size); 1285 | position += size; 1286 | } 1287 | 1288 | [[gnu::always_inline]] inline bool UnserializeSafe(void *const p, const uint32_t size) 1289 | { 1290 | memcpy(p, buffer + position, size); 1291 | position += size; 1292 | 1293 | return true; 1294 | } 1295 | 1296 | inline uint32_t ToEndSpanLen() const 1297 | { 1298 | return length_ - position; 1299 | } 1300 | 1301 | inline strwlen32_t SuffixFromOffset() const 1302 | { 1303 | return {buffer + position, length_ - position}; 1304 | } 1305 | }; 1306 | 1307 | template 1308 | static inline void PrintImpl(Buffer &out, const range_base &r) 1309 | { 1310 | out.AppendFmt("[%" PRIu64 ", %" PRIu64 ")", uint64_t(r.Left()), uint64_t(r.Right())); 1311 | } 1312 | 1313 | struct _srcline_repr 1314 | { 1315 | uint32_t line; 1316 | const char *func; 1317 | const char *file; 1318 | 1319 | _srcline_repr(const uint32_t l, const char *const f, const char *const _file) 1320 | : line{l}, func{f}, file{_file} 1321 | { 1322 | } 1323 | 1324 | _srcline_repr(const _srcline_repr &o) 1325 | : line{o.line}, func{o.func}, file{o.file} 1326 | { 1327 | } 1328 | 1329 | auto &operator=(const _srcline_repr &o) 1330 | { 1331 | line = o.line; 1332 | func = o.func; 1333 | file = o.file; 1334 | 1335 | return *this; 1336 | } 1337 | }; 1338 | 1339 | #define srcline_repr() _srcline_repr(__LINE__, __FUNCTION__, __FILE__) 1340 | static inline void PrintImpl(Buffer &out, const _srcline_repr &r) 1341 | { 1342 | out.AppendFmt("<%s:%u %s>", r.file, r.line, r.func); 1343 | } 1344 | 1345 | 1346 | struct duration_repr 1347 | { 1348 | const uint64_t time; 1349 | 1350 | duration_repr(const uint64_t timeInMicroseconds) 1351 | : time{timeInMicroseconds} 1352 | { 1353 | } 1354 | 1355 | duration_repr(const struct timeval &tv) 1356 | : time{uint64_t(tv.tv_sec * 1000000UL + tv.tv_usec)} 1357 | { 1358 | } 1359 | 1360 | strwlen8_t Get(char *const out) const 1361 | { 1362 | uint8_t len; 1363 | 1364 | if (time < 1000) 1365 | len = sprintf(out, "%uus", (uint32_t)time); 1366 | else if (time < 1000000) 1367 | { 1368 | len = sprintf(out, "%.3f", (double)time / 1000000.0); 1369 | while (len && (out[len - 1] == '0')) 1370 | --len; 1371 | if (len && out[len - 1] == '.') 1372 | --len; 1373 | out[len++] = 's'; 1374 | } 1375 | else if (time < 60 * 1000000) 1376 | { 1377 | len = sprintf(out, "%.3f", (double)time / 1000000.0); 1378 | while (len && (out[len - 1] == '0')) 1379 | --len; 1380 | if (len && out[len - 1] == '.') 1381 | --len; 1382 | out[len++] = 's'; 1383 | } 1384 | else if (time < 3600UL * 1000000UL) 1385 | { 1386 | len = sprintf(out, "%um%u", (uint32_t)(time / (60 * 1000000UL)), (uint32_t)((time % (60 * 1000000UL)) / 1000000UL)); 1387 | while (len && out[len - 1] == '0') 1388 | --len; 1389 | if (len && out[len - 1] != 'm') 1390 | out[len++] = 's'; 1391 | } 1392 | else 1393 | { 1394 | const uint32_t seconds = time / 1000000UL; 1395 | 1396 | if (const auto mins = (seconds % 3600) / 60) 1397 | len = sprintf(out, "%uh%um", seconds / 3600, mins); 1398 | else 1399 | len = sprintf(out, "%uh", seconds / 3600); 1400 | } 1401 | 1402 | return {out, len}; 1403 | } 1404 | }; 1405 | 1406 | static inline void PrintImpl(Buffer &out, const duration_repr &r) 1407 | { 1408 | out.reserve(32); 1409 | out.AdvanceLength(r.Get(out.end()).len); 1410 | } 1411 | -------------------------------------------------------------------------------- /Switch/switch_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "switch_numops.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html 11 | // returns true if this is a static const, most likely allocated in RODATA segment 12 | // This is _very_ useful, for we can check if that's the case and just don't alloc()/copy/free() data instead just point to them 13 | extern char etext, edata, end; 14 | 15 | template 16 | [[gnu::always_inline]] inline static bool IsConstant(T *const expr) 17 | { 18 | if (__builtin_constant_p(expr)) 19 | return true; 20 | else 21 | { 22 | // This is only valid in either a macro or an inline function. 23 | // However, if you use it in an inlined function and pass an argument of the function as the argument to the built-in, 24 | // GCC never returns when you call the inline function with a string constant or compount literal, and does not return 1 when you 25 | // pass a constant number value to the inline function unless you specify the -o opetion 26 | // 27 | // So what really need to do is perhaps check if this is in RODATA segment, but how to do that? 28 | // Turns out, we can 29 | // Ref: http://stackoverflow.com/questions/4308996/finding-the-address-range-of-the-data-segment 30 | // Ref: http://manpages.ubuntu.com/manpages/jaunty/man3/end.3.html 31 | const uintptr_t addr = (uintptr_t)expr; 32 | 33 | return addr >= (uintptr_t)&etext && addr < (uintptr_t)&edata; 34 | } 35 | } 36 | 37 | template 38 | struct strwithlen 39 | { 40 | const CT *p; 41 | LT len; 42 | 43 | using iterator = const CT *; 44 | using value_type = CT; 45 | 46 | strwithlen Div(const CT c) 47 | { 48 | if (const auto *const p = Search(c)) 49 | { 50 | const auto res = SuffixFrom(p + 1); 51 | 52 | SetEnd(p); 53 | return res; 54 | } 55 | else 56 | return {}; 57 | } 58 | 59 | std::pair Divided(const CT c) const 60 | { 61 | if (const auto *const p = Search(c)) 62 | return {PrefixUpto(p), SuffixFrom(p + 1)}; 63 | else 64 | return {{this->p, this->len}, {}}; 65 | } 66 | 67 | // warning: with copy assignment operator not allowed in union 68 | // UODATE: Well, we don't care now, C++11 allows for non PODs in unions as long as you initialize them 69 | strwithlen &operator=(const strwithlen &o) 70 | { 71 | p = o.p; 72 | len = o.len; 73 | return *this; 74 | } 75 | 76 | strwithlen &operator=(const CT *const s) 77 | { 78 | p = s; 79 | len = strlen(s); 80 | return *this; 81 | } 82 | 83 | [[gnu::always_inline]] inline bool IsConstant() 84 | { 85 | return p == nullptr || ::IsConstant(p); 86 | } 87 | 88 | inline uint8_t constexpr Length() const 89 | { 90 | return len; 91 | } 92 | 93 | strwithlen SubstringFrom(const CT *const s) const 94 | { 95 | return strwithlen(s, (p + len) - s); 96 | } 97 | 98 | strwithlen SubstringFrom(const LT o) const // Equivalent to SuffixFrom() 99 | { 100 | return strwithlen(p + o, len - o); 101 | } 102 | 103 | [[gnu::always_inline]] inline constexpr CT LastChar() const 104 | { 105 | return p[len - 1]; 106 | } 107 | 108 | typename std::enable_if::value, LT>::type AsEscaped(CT *const out, const uint32_t available) const 109 | { 110 | 111 | return AsEscapedImpl((char *)p, len, (char *)out, available); 112 | } 113 | 114 | static inline uint8_t ToDec(const char c) 115 | { 116 | if (c >= 'a' && c <= 'f') 117 | return c - 'a' + 10; 118 | else if (c >= 'A' && c <= 'F') 119 | return c - 'A' + 10; 120 | else if (isdigit(c)) 121 | return c - '0'; 122 | else 123 | return UINT8_MAX; 124 | } 125 | 126 | LT CountOf(const CT c) const 127 | { 128 | LT res{0}; 129 | 130 | for (const CT *it = p, *const e = it + len; it != e; ++it) 131 | { 132 | if (*it == c) 133 | ++res; 134 | } 135 | return res; 136 | } 137 | 138 | LT CountOf(const strwithlen needle) const 139 | { 140 | strwithlen in(p, len); 141 | LT cnt{0}; 142 | 143 | while (const CT *const p = in.Search(needle)) 144 | in.AdvanceTo(p + needle.len); 145 | 146 | return cnt; 147 | } 148 | 149 | [[gnu::always_inline]] inline void FreeIfNotConstant() 150 | { 151 | if (IsConstant() == false) 152 | free(const_cast(p)); 153 | p = nullptr; 154 | } 155 | 156 | inline const CT *Search(const CT c) const 157 | { 158 | if (sizeof(CT) != sizeof(char)) 159 | { 160 | for (const CT *it = p, *const e = p + len; it != e; ++it) 161 | { 162 | if (*it == c) 163 | return it; 164 | } 165 | return nullptr; 166 | } 167 | else 168 | return (char *)memchr((void *)p, c, len); 169 | } 170 | 171 | inline const typename std::enable_if::value, CT *>::type SearchR(const char c) const 172 | { 173 | return (CT *)memrchr((void *)p, c, len); 174 | } 175 | 176 | inline typename std::enable_if::value, CT *>::type SearchRWithLimit(const CT c, const LT limit) const 177 | { 178 | const auto l = std::min(len, limit); 179 | 180 | return (CT *)memrchr(p + len - l, c, l); 181 | } 182 | 183 | inline const CT *Search(const strwithlen needle) const 184 | { 185 | if (sizeof(CT) == sizeof(char)) 186 | { 187 | return (char *)memmem(p, len, needle.p, needle.len); 188 | } 189 | else 190 | { 191 | const CT *const res = std::search(p, p + len, needle.p, needle.p + needle.len); 192 | 193 | return res == p + len ? nullptr : res; 194 | } 195 | } 196 | 197 | inline const CT *Search(const CT *const needle, const LT needleLen) const 198 | { 199 | return Search(strwithlen(needle, needleLen)); 200 | } 201 | 202 | // We can now deal with this just fine, C++11 makes it possible 203 | constexpr strwithlen() 204 | : p{nullptr}, len{0} 205 | { 206 | } 207 | 208 | strwithlen(const char *const s) 209 | : p{s}, len(strlen(s)) 210 | { 211 | } 212 | 213 | strwithlen(const char *const s, const char *const e) 214 | : p{s}, len{LT(e - s)} 215 | { 216 | } 217 | 218 | int constexpr Cmp(const CT *const s, const LT l) const 219 | { 220 | return l == len ? memcmp(p, s, l) 221 | : len < l ? ({const auto r = memcmp(p, s, len); r == 0 ? -1 : r; }) 222 | : ({const auto r = memcmp(p, s, l); r == 0 ? 1 : r; }); 223 | } 224 | 225 | inline int constexpr Cmp(const strwithlen *const o) const 226 | { 227 | return Cmp(o->p, o->len); 228 | } 229 | 230 | inline int constexpr Cmp(const strwithlen &o) const 231 | { 232 | return Cmp(o.p, o.len); 233 | } 234 | 235 | inline constexpr bool operator<(const strwithlen &o) const 236 | { 237 | return Cmp(&o) < 0; 238 | } 239 | 240 | inline constexpr bool operator>(const strwithlen &o) const 241 | { 242 | return Cmp(&o) > 0; 243 | } 244 | 245 | [[gnu::always_inline]] inline static uint32_t MaxLength() 246 | { 247 | static const uint64_t lens[] = {0, UINT8_MAX, UINT16_MAX, 0, UINT32_MAX, 0, 0, 0, UINT64_MAX}; 248 | return lens[sizeof(LT)]; 249 | } 250 | 251 | // using const uint32_t l not const LT l to silence compiler warnings 252 | strwithlen(const CT *const s, const uint32_t l) 253 | : p(s) 254 | { 255 | assert(l <= MaxLength()); 256 | 257 | len = l; 258 | } 259 | 260 | [[gnu::always_inline]] inline constexpr operator bool() const 261 | { 262 | return len; 263 | } 264 | 265 | inline bool constexpr operator==(const strwithlen &o) const 266 | { 267 | return len == o.len && memcmp(p, o.p, len) == 0; 268 | } 269 | 270 | inline bool constexpr operator!=(const strwithlen &o) const 271 | { 272 | return len != o.len || memcmp(p, o.p, len); 273 | } 274 | 275 | // See range_base::Contains() 276 | inline bool constexpr Contains(const CT *const ptr) const 277 | { 278 | return sizeof(CT) == 8 279 | ? ptr >= p && ptr < (p + len) 280 | : uint32_t(ptr - p) < len; 281 | } 282 | 283 | inline bool constexpr Contains(const char *const op, const LT olen) const 284 | { 285 | return op >= p && op + olen <= p + len; 286 | } 287 | 288 | strwithlen Substr(const LT o, const LT l) const 289 | { 290 | assert(o + l <= len); 291 | 292 | return {p + o, l}; 293 | } 294 | 295 | strwithlen Inset(const LT l, const LT r) const 296 | { 297 | const auto n = l + r; 298 | 299 | assert(n <= len); 300 | 301 | assert(n <= len); 302 | return {p + l, len - n}; 303 | } 304 | 305 | template 306 | inline bool constexpr Contains(const T &s) const 307 | { 308 | return Contains(s.p, s.len); 309 | } 310 | 311 | template 312 | inline bool constexpr Contains(const T *const s) const 313 | { 314 | return Contains(*s); 315 | } 316 | 317 | inline bool Intersects(const CT *const op, const LT olen) const 318 | { 319 | const auto *const e = p + len, *const eo = op + olen; 320 | 321 | return e >= op && p <= eo; 322 | } 323 | 324 | template 325 | inline bool Intersects(const T &s) const 326 | { 327 | return Intersects(s.p, s.len); 328 | } 329 | 330 | template 331 | inline bool Intersects(const T *const s) const 332 | { 333 | return Intersects(*s); 334 | } 335 | 336 | void Unset() 337 | { 338 | p = nullptr; 339 | len = 0; 340 | } 341 | 342 | // Not using const LT l, so that compiler won't have to complain about missing casts 343 | void Set(const CT *const ptr, const uint32_t l) 344 | { 345 | len = l; 346 | p = ptr; 347 | } 348 | 349 | typename std::enable_if::value>::type Set(const char *const ptr) 350 | { 351 | p = ptr; 352 | SetLengthExpl(strlen((char *)p)); 353 | } 354 | 355 | inline bool IsDigits() const 356 | { 357 | const CT *it = p, *const e = it + len; 358 | 359 | while (likely(it != e)) 360 | { 361 | if (!isdigit(*it)) 362 | return false; 363 | ++it; 364 | } 365 | 366 | return len; 367 | } 368 | 369 | bool operator==(const CT *ptr) const 370 | { 371 | const auto *it = p; 372 | const auto *const end = p + len; 373 | 374 | while (it != end) 375 | { 376 | if (*it != *ptr) 377 | return false; 378 | ++it; 379 | ++ptr; 380 | } 381 | 382 | return *ptr == '\0'; 383 | } 384 | 385 | double AsDouble() const 386 | { 387 | const auto *it = p, *const e = p + len; 388 | double sign; 389 | 390 | if (it == e) 391 | return 0; // strtod() returns 0 for empty input 392 | else if (*it == '-') 393 | { 394 | ++it; 395 | sign = -1; 396 | } 397 | else if (unlikely(*it == '+')) 398 | { 399 | ++it; 400 | sign = 1; 401 | } 402 | else 403 | sign = 1; 404 | 405 | double v{0}; 406 | 407 | do 408 | { 409 | if (*it == '.' || *it == ',') // support both radix characters 410 | { 411 | double exp{0}; 412 | // We could just use: double pow10{10.0} and in each iteration 413 | // pow10*=10.0 414 | // and then just use v += exp * (1.0l / pow10) 415 | // but because we usualyl expect a digit digits for the exponents, we 'll just use a switch to avoid 416 | // the multiplication 417 | // UPDATE: nevermind, we 'll do that later 418 | double pow10{1.0}; // faster than pow(exp, totalExpDigits) 419 | // we could use case(totalExpDigits) in order to avoid this, or just use 420 | //constexpr uint64_t scale[] = {pow(10, 1), pow(10, 2), pow(10, 3), pow(10, 4), pow(10, 5), pow(10, 6), pow(10, 7), ... 421 | 422 | for (++it; it != e; ++it) 423 | { 424 | if (likely(isdigit(*it))) 425 | { 426 | exp = exp * 10 + (*it - '0'); 427 | pow10 *= 10.0; 428 | } 429 | else 430 | { 431 | // We could have handled exponent (e|E) 432 | // See: http://www.leapsecond.com/tools/fast_atof.c 433 | // but it's not really worth it; we never use it 434 | return NAN; 435 | } 436 | } 437 | 438 | return (v + (exp * (1.0L / pow10))) * sign; 439 | } 440 | else if (likely(isdigit(*it))) 441 | v = v * 10 + (*(it++) - '0'); 442 | else 443 | return NAN; 444 | 445 | } while (it != e); 446 | 447 | return v * sign; 448 | } 449 | 450 | typename std::enable_if::value, uint32_t>::type AsUint32() const 451 | { 452 | static constexpr uint32_t pow10[10] = 453 | { 454 | 1000000000ul, 455 | 100000000ul, 456 | 10000000ul, 457 | 1000000ul, 458 | 100000ul, 459 | 10000ul, 460 | 1000ul, 461 | 100ul, 462 | 10ul, 463 | 1ul, 464 | }; 465 | 466 | // this test() and the test for d >= 10 really impact performance 467 | // so just don't do it 468 | if (unlikely(len > 10)) 469 | { 470 | // throw something? 471 | return 0; 472 | } 473 | 474 | uint32_t res{0}, k{0}; 475 | 476 | for (uint32_t i = sizeof_array(pow10) - len; k != len; ++i) 477 | { 478 | const auto d = unsigned(p[k++]) - '0'; 479 | 480 | #if 0 481 | if (unlikely(d >= 10)) 482 | { 483 | // throw something? 484 | return 0; 485 | } 486 | #endif 487 | 488 | res += pow10[i] * d; 489 | } 490 | 491 | return res; 492 | } 493 | 494 | int32_t AsInt32() const 495 | { 496 | const auto *it = p, *const e = it + len; 497 | 498 | if (it != e) 499 | { 500 | int32_t v{0}; 501 | 502 | if (*it == '-') 503 | { 504 | for (++it; it != e && isdigit(*it); ++it) 505 | v = v * 10 + (*it - '0'); 506 | 507 | return -v; 508 | } 509 | else 510 | { 511 | for (; it != e && isdigit(*it); ++it) 512 | v = v * 10 + (*it - '0'); 513 | return v; 514 | } 515 | } 516 | 517 | return 0; 518 | } 519 | 520 | // See AsUint32() comments 521 | typename std::enable_if::value, uint64_t>::type AsUint64() const 522 | { 523 | static constexpr uint64_t pow10[20] __attribute__((__aligned__(64))) = // 20 because 20 digits are enough for a 64bit number 524 | { 525 | 10000000000000000000ul, 526 | 1000000000000000000ul, 527 | 100000000000000000ul, 528 | 10000000000000000ul, 529 | 1000000000000000ul, 530 | 100000000000000ul, 531 | 10000000000000ul, 532 | 1000000000000ul, 533 | 100000000000ul, 534 | 10000000000ul, 535 | 1000000000ul, 536 | 100000000ul, 537 | 10000000ul, 538 | 1000000ul, 539 | 100000ul, 540 | 10000ul, 541 | 1000ul, 542 | 100ul, 543 | 10ul, 544 | 1ul, 545 | }; 546 | 547 | if (unlikely(len > 20)) 548 | { 549 | // throw something? 550 | return 0; 551 | } 552 | 553 | uint64_t res{0}; 554 | uint32_t k{0}; 555 | 556 | for (uint32_t i = sizeof_array(pow10) - len; k != len; ++i) 557 | { 558 | const auto d = unsigned(p[k++]) - '0'; 559 | 560 | res += pow10[i] * d; 561 | } 562 | 563 | return res; 564 | } 565 | 566 | [[gnu::always_inline]] inline bool Eq(const CT *const ptr) const 567 | { 568 | return operator==(ptr); 569 | } 570 | 571 | [[gnu::always_inline]] inline bool Eq(const CT *const v, const LT l) const 572 | { 573 | return l == len && memcmp(v, p, l) == 0; 574 | } 575 | 576 | [[gnu::always_inline]] inline typename std::enable_if::value, bool>::type EqNoCase(const CT *const v, const LT l) const 577 | { 578 | return l == len ? !strncasecmp((char *)v, (char *)p, l) : false; 579 | } 580 | 581 | typename std::enable_if::value, bool>::type EqNoCase(const CT *v) const 582 | { 583 | const auto *it = p, *const e = p + len; 584 | 585 | while (it != e && toupper(*v) == toupper(*it)) 586 | { 587 | ++it; 588 | ++v; 589 | } 590 | 591 | return it == e && *v == '\0'; 592 | } 593 | 594 | [[gnu::always_inline]] inline bool EqNoCase(const strwithlen &o) const 595 | { 596 | return EqNoCase(o.p, o.len); 597 | } 598 | 599 | [[gnu::always_inline]] inline bool IsEqual(const CT *const ptr, const LT l) const 600 | { 601 | return l == len && memcmp(p, ptr, l) == 0; 602 | } 603 | 604 | inline bool EndsWith(const CT *const v, const LT l) const 605 | { 606 | return l <= len && memcmp(p + len - l, v, l) == 0; 607 | } 608 | 609 | // useful for hostnames, e.g 610 | // delimEndsWith(_S(".google.com")) will return true for both "google.com", "www.google.com" 611 | // the first character is considered the delimeter 612 | bool delimEndsWith(const CT *const v, const LT lt) const 613 | { 614 | if (!lt) 615 | return true; 616 | 617 | return EndsWith(v + 1, lt - 1) && (len == lt - 1 || p[len - lt] == v[0]); 618 | } 619 | 620 | inline bool EndsWithButNoExactMatch(const CT *const v, const LT l) const 621 | { 622 | return l < len && memcmp(p + len - l, v, l) == 0; 623 | } 624 | 625 | inline bool EndsWith(const CT *const v) const 626 | { 627 | return EndsWith(v, strlen(v)); 628 | } 629 | 630 | inline bool EndsWithNoCase(const CT *const v) const 631 | { 632 | return EndsWithNoCase(v, strlen(v)); 633 | } 634 | 635 | inline typename std::enable_if::value, bool>::type EndsWithNoCase(const CT *const v, const LT l) const 636 | { 637 | return l <= len && strncasecmp((char *)p + len - l, (char *)v, l) == 0; 638 | } 639 | 640 | inline typename std::enable_if::value, bool>::type BeginsWith(const CT *const v, const LT l) const 641 | { 642 | return l <= len && memcmp(p, v, l) == 0; 643 | } 644 | 645 | inline bool EndsWith(const CT c) const 646 | { 647 | return len && p[len - 1] == c; 648 | } 649 | 650 | inline bool HasPrefix(const CT *const v, const LT l) const 651 | { 652 | return BeginsWith(v, l); 653 | } 654 | 655 | inline bool BeginsWith(const CT c) const 656 | { 657 | return likely(len) ? *p == c : false; 658 | } 659 | 660 | inline bool HasPrefix(const CT c) const 661 | { 662 | return BeginsWith(c); 663 | } 664 | 665 | strwithlen Prefix(const LT l) const 666 | { 667 | return strwithlen(p, std::min(len, l)); 668 | } 669 | 670 | strwithlen Suffix(const LT l) const 671 | { 672 | return strwithlen(End() - l, l); 673 | } 674 | 675 | strwithlen SuffixFrom(const CT *const offset) const 676 | { 677 | return strwithlen(offset, End() - offset); 678 | } 679 | 680 | strwithlen FirstDigitsSeq() const 681 | { 682 | // handy utility function 683 | for (const char *it = p, *const e = End();; ++it) 684 | { 685 | if (isdigit(*it)) 686 | { 687 | const char *const b = it; 688 | 689 | for (++it; it != e && isdigit(*it); ++it) 690 | continue; 691 | 692 | return {b, it}; 693 | } 694 | } 695 | return {}; 696 | } 697 | 698 | strwithlen SuffixFrom(const LT o) const 699 | { 700 | return SuffixFrom(p + o); 701 | } 702 | 703 | strwithlen PrefixUpto(const CT *const o) const 704 | { 705 | return strwithlen(p, o - p); 706 | } 707 | 708 | // e.g if (name.Extension().Eq(_S("png"))) { .. } 709 | strwithlen Extension(const CT c = '.', const LT maxLength = 16) const 710 | { 711 | if (const auto *const it = SearchRWithLimit(c, maxLength)) 712 | return SuffixFrom(it + 1); 713 | else 714 | return {}; 715 | } 716 | 717 | inline typename std::enable_if::value, bool>::type BeginsWithNoCase(const CT *const v, const LT l) const 718 | { 719 | return l <= len && strncasecmp((char *)p, (char *)v, l) == 0; 720 | } 721 | 722 | bool BeginsWith(const CT *ptr) const 723 | { 724 | const auto *it = p; 725 | const auto *const end = p + len; 726 | 727 | while (it != end) 728 | { 729 | if (*it != *ptr) 730 | return false; 731 | 732 | if (++it == end) 733 | return true; 734 | ++ptr; 735 | } 736 | 737 | return *ptr == '\0'; 738 | } 739 | 740 | bool HasPrefix(const CT *ptr) const 741 | { 742 | return BeginsWith(ptr); 743 | } 744 | 745 | template 746 | inline bool BeginsWith(const T &s) const 747 | { 748 | return BeginsWith(s.p, s.len); 749 | } 750 | 751 | template 752 | inline bool BeginsWith(const T *const s) const 753 | { 754 | return BeginsWith(s); 755 | } 756 | 757 | template 758 | inline bool EndsWith(const T *const s) const 759 | { 760 | return EndsWith(*s); 761 | } 762 | 763 | [[gnu::always_inline]] inline const CT *End() const 764 | { 765 | return p + len; 766 | } 767 | 768 | void Extend(const LT l) 769 | { 770 | len += l; 771 | } 772 | 773 | inline void SetLengthExpl(const LT l) 774 | { 775 | assert(l <= MaxLength()); 776 | len = l; 777 | } 778 | 779 | void InitWithCopy(const void *const s, const LT l) 780 | { 781 | SetLengthExpl(l); 782 | 783 | if (len) 784 | { 785 | auto *const ptr = (CT *)malloc(len * sizeof(CT)); 786 | 787 | assert(ptr != nullptr); 788 | memcpy(ptr, s, len * sizeof(CT)); 789 | p = ptr; 790 | } 791 | else 792 | p = nullptr; 793 | } 794 | 795 | typename std::enable_if::value, CT *>::type ToCString() const 796 | { 797 | auto *const r = (CT *)malloc((len * sizeof(CT)) + 1); 798 | 799 | assert(r != nullptr); 800 | memcpy(r, p, len * sizeof(CT)); 801 | r[len] = '\0'; 802 | return r; 803 | } 804 | 805 | typename std::enable_if::value, CT *>::type ToCString(CT *const out) const 806 | { 807 | memcpy(out, p, len * sizeof(CT)); 808 | out[len] = '\0'; 809 | return out; 810 | } 811 | 812 | typename std::enable_if::value, CT *>::type ToCString(CT *const out, const uint32_t outSize) const 813 | { 814 | assert(len + 1 <= outSize); 815 | memcpy(out, p, len * sizeof(CT)); 816 | out[len] = '\0'; 817 | return out; 818 | } 819 | 820 | CT *Copy() const 821 | { 822 | auto *const r = (CT *)malloc(len * sizeof(CT)); 823 | 824 | assert(r != nullptr); 825 | memcpy(r, p, len * sizeof(CT)); 826 | return r; 827 | } 828 | 829 | [[gnu::always_inline]] inline const CT *At(const LT o) const 830 | { 831 | return p + o; 832 | } 833 | 834 | CT *CopyTo(CT *const to) const 835 | { 836 | memcpy(to, p, len * sizeof(CT)); 837 | return to + len; 838 | } 839 | 840 | CT *asLowercase(CT *const out) const 841 | { 842 | for (LT i{0}; i != len; ++i) 843 | out[i] = tolower(p[i]); 844 | 845 | return out; 846 | } 847 | 848 | void InitWithCopy(const strwithlen &o) 849 | { 850 | InitWithCopy(o.p, o.len); 851 | } 852 | 853 | void InitWithCopy(const strwithlen *const o) 854 | { 855 | InitWithCopy(o->p, o->len); 856 | } 857 | 858 | inline void AdjustRight(const LT v) 859 | { 860 | len -= v; 861 | } 862 | 863 | inline void AdjustLeft(const LT v) 864 | { 865 | p += v; 866 | len -= v; 867 | } 868 | 869 | LT CommonPrefixLen(const strwithlen o) const 870 | { 871 | const auto *it = p; 872 | 873 | for (const auto *oit = o.p, *const oend = oit + o.len, *const end = p + len; 874 | it != end && oit != oend && *it == *oit; 875 | ++it, ++oit) 876 | { 877 | continue; 878 | } 879 | 880 | return it - p; 881 | } 882 | 883 | // This really only makes sense in very specific situations 884 | // for usually p[len] is not accessible 885 | inline auto isNullTerminated() const 886 | { 887 | return !p[len]; 888 | } 889 | 890 | inline strwithlen CommonPrefix(const strwithlen o) const 891 | { 892 | return strwithlen(p, CommonPrefixLen(o)); 893 | } 894 | 895 | LT CommonSuffixLen(const strwithlen o) const 896 | { 897 | const auto *const e = End(), *const oend = o.End(), *const op = o.p; 898 | const auto *it = e; 899 | 900 | for (const auto *oit = oend; 901 | oit != op && it != p && oit[-1] == it[-1]; 902 | --it, --oit) 903 | { 904 | continue; 905 | } 906 | 907 | return e - it; 908 | } 909 | 910 | // Excluding common prefix and suffixes of @o 911 | // e.g "STARWARSCRAFT".IntersectionOf("STARCRAFT") = "WARS" 912 | inline strwithlen IntersectionOf(const strwithlen o) const 913 | { 914 | const auto *const from = p + CommonPrefixLen(o); 915 | const auto *const upto = End() - CommonSuffixLen(o); 916 | 917 | return strwithlen(from, upto - from); 918 | } 919 | 920 | inline strwithlen CommonSuffix(const strwithlen o) const 921 | { 922 | return SuffixFrom(len - CommonSuffixLen(o)); 923 | } 924 | 925 | inline void StripPrefix(const LT v) 926 | { 927 | AdjustLeft(v); 928 | } 929 | 930 | strwithlen AsTrimmedBy(const LT l) const 931 | { 932 | strwithlen res{p, len}; 933 | 934 | res.StripSuffix(l); 935 | return res; 936 | } 937 | 938 | bool StripPrefix(const CT *const s, const LT l) 939 | { 940 | if (BeginsWith(s, l)) 941 | { 942 | StripPrefix(l); 943 | return true; 944 | } 945 | else 946 | return false; 947 | } 948 | 949 | bool StripSuffix(const CT *const s, const LT l) 950 | { 951 | if (EndsWith(s, l)) 952 | { 953 | StripSuffix(l); 954 | return true; 955 | } 956 | else 957 | return false; 958 | } 959 | 960 | inline void StripTrailingCharacter(const CT c) 961 | { 962 | while (len && p[len - 1] == c) 963 | --len; 964 | } 965 | 966 | inline void StripInitialCharacter(const CT c) 967 | { 968 | while (len && *p == c) 969 | { 970 | ++p; 971 | --len; 972 | } 973 | } 974 | 975 | inline void StripSuffix(const LT v) 976 | { 977 | AdjustRight(v); 978 | } 979 | 980 | bool InRange(const CT *const s) const 981 | { 982 | return s >= p && s < p + len; 983 | } 984 | 985 | inline void AdvanceTo(const CT *const to) 986 | { 987 | len -= to - p; 988 | p = to; 989 | } 990 | 991 | void SetEnd(const CT *const e) 992 | { 993 | len = e - p; 994 | } 995 | 996 | void SetEndTo(const CT c) 997 | { 998 | if (const auto *const res = Search(c)) 999 | SetEnd(res); 1000 | } 1001 | 1002 | const CT *NextWS() const 1003 | { 1004 | for (const auto *it = p, *const end = End(); it != end; ++it) 1005 | { 1006 | if (isspace(*it)) 1007 | return it; 1008 | } 1009 | return nullptr; 1010 | } 1011 | 1012 | inline LT OffsetAt(const CT *const it) const 1013 | { 1014 | return it - p; 1015 | } 1016 | 1017 | inline strwithlen Replica() const 1018 | { 1019 | return {p, len}; 1020 | } 1021 | 1022 | strwithlen &TrimWS() 1023 | { 1024 | while (len && isspace(*p)) 1025 | { 1026 | ++p; 1027 | --len; 1028 | } 1029 | while (len && isspace(p[len - 1])) 1030 | --len; 1031 | return *this; 1032 | } 1033 | 1034 | bool IsBlank() const 1035 | { 1036 | for (const auto *it = p, *const end = p + len; it != end; ++it) 1037 | { 1038 | if (!isspace(*it)) 1039 | return false; 1040 | } 1041 | return true; 1042 | } 1043 | 1044 | const CT *begin() const 1045 | { 1046 | return p; 1047 | } 1048 | 1049 | const CT *end() const 1050 | { 1051 | return p + len; 1052 | } 1053 | 1054 | const CT *data() const 1055 | { 1056 | return p; 1057 | } 1058 | 1059 | struct _segments 1060 | { 1061 | const strwithlen s; 1062 | const CT sep; 1063 | 1064 | struct iterator 1065 | { 1066 | strwithlen cur; 1067 | const CT sep; 1068 | const CT *next; 1069 | 1070 | void Next() 1071 | { 1072 | const auto *const e = cur.End(); 1073 | 1074 | while (next != e && *next != sep) 1075 | ++next; 1076 | } 1077 | 1078 | iterator(const strwithlen input, const CT c) 1079 | : cur(input), sep(c), next(cur.p) 1080 | { 1081 | Next(); 1082 | } 1083 | 1084 | bool operator!=(const iterator &o) const 1085 | { 1086 | return cur.p != o.cur.p; 1087 | } 1088 | 1089 | iterator &operator++() 1090 | { 1091 | cur.AdvanceTo(next); 1092 | if (cur) 1093 | { 1094 | cur.AdjustLeft(1); 1095 | ++next; 1096 | Next(); 1097 | } 1098 | return *this; 1099 | } 1100 | 1101 | inline strwithlen operator*() const 1102 | { 1103 | return strwithlen(cur.p, next - cur.p); 1104 | } 1105 | }; 1106 | 1107 | _segments(const strwithlen in, const CT c) 1108 | : s(in), sep(c) 1109 | { 1110 | } 1111 | 1112 | iterator begin() const 1113 | { 1114 | return iterator(s, sep); 1115 | } 1116 | 1117 | iterator end() const 1118 | { 1119 | return iterator({s.End(), uint32_t(0)}, sep); 1120 | } 1121 | }; 1122 | 1123 | template 1124 | struct _segmentsF 1125 | { 1126 | const strwithlen s; 1127 | F &l; 1128 | 1129 | struct iterator 1130 | { 1131 | strwithlen cur; 1132 | F &l; 1133 | const CT *next; 1134 | 1135 | void Next() 1136 | { 1137 | const auto *const e = cur.end(); 1138 | 1139 | while (next != e && !l(*next)) 1140 | ++next; 1141 | } 1142 | 1143 | iterator(const strwithlen input, F &lambda) 1144 | : cur(input), l{lambda}, next(cur.p) 1145 | { 1146 | Next(); 1147 | } 1148 | 1149 | bool operator!=(const iterator &o) const 1150 | { 1151 | return cur.p != o.cur.p; 1152 | } 1153 | 1154 | iterator &operator++() 1155 | { 1156 | cur.AdvanceTo(next); 1157 | if (cur) 1158 | { 1159 | cur.AdjustLeft(1); 1160 | ++next; 1161 | Next(); 1162 | } 1163 | return *this; 1164 | } 1165 | 1166 | inline strwithlen operator*() const 1167 | { 1168 | return strwithlen(cur.p, next - cur.p); 1169 | } 1170 | }; 1171 | 1172 | _segmentsF(const strwithlen in, F &lambda) 1173 | : s(in), l(lambda) 1174 | { 1175 | } 1176 | 1177 | iterator begin() const 1178 | { 1179 | return iterator(s, l); 1180 | } 1181 | 1182 | iterator end() const 1183 | { 1184 | return iterator({s.End(), uint32_t(0)}, l); 1185 | } 1186 | }; 1187 | 1188 | // e.g for (const auto it : strwlen32_t("com.markpadakis.apps").Segments('.') { .. } 1189 | auto Segments(const CT separator) const 1190 | { 1191 | return _segments(*this, separator); 1192 | } 1193 | 1194 | auto Split(const CT separator) const 1195 | { 1196 | return _segments(*this, separator); 1197 | } 1198 | 1199 | template 1200 | auto splitL(F &&l) const 1201 | { 1202 | return _segmentsF(*this, l); 1203 | } 1204 | 1205 | uint32_t SplitInto(const CT separator, strwithlen *const out, const size_t capacity) const 1206 | { 1207 | if (!len) 1208 | return 0; 1209 | 1210 | uint32_t n{0}; 1211 | auto it = p; 1212 | 1213 | out->p = it; 1214 | for (const auto *const e = end(); it != e;) 1215 | { 1216 | if (*it == separator) 1217 | { 1218 | out[n++].SetEnd(it); 1219 | if (n == capacity) 1220 | return UINT32_MAX; 1221 | out[n].p = ++it; 1222 | } 1223 | else 1224 | ++it; 1225 | } 1226 | out[n++].SetEnd(it); 1227 | 1228 | return n; 1229 | } 1230 | 1231 | // TODO: 1232 | // __keyvalues KeyValues(const char sep = ',', const bool ignoreBlanks = true, const bool kvSep = '=') const 1233 | }; 1234 | 1235 | typedef strwithlen strwithlen64_t, strwlen64_t; 1236 | typedef strwithlen strwithlen32_t, strwlen32_t; 1237 | typedef strwithlen strwithlen16_t, strwlen16_t; 1238 | typedef strwithlen strwithlen8_t, strwlen8_t; 1239 | 1240 | [[gnu::always_inline]] inline static auto S32(const char *const p, const uint32_t len) 1241 | { 1242 | return strwlen32_t(p, len); 1243 | } 1244 | 1245 | [[gnu::always_inline]] inline static auto S8(const char *const p, const uint32_t len) 1246 | { 1247 | return strwlen8_t(p, len); 1248 | } 1249 | 1250 | [[gnu::always_inline]] inline static auto S16(const char *const p, const uint32_t len) 1251 | { 1252 | return strwlen16_t(p, len); 1253 | } 1254 | 1255 | #define _S32(s) strwlen32_t(s, STRLEN(s)) 1256 | #define _S16(s) strwlen16_t(s, STRLEN(s)) 1257 | #define _S8(s) strwlen8_t(s, STRLEN(s)) 1258 | 1259 | #ifdef LEAN_SWITCH 1260 | namespace std 1261 | { 1262 | template 1263 | struct hash> 1264 | { 1265 | using argument_type = strwithlen; 1266 | using result_type = std::size_t; 1267 | 1268 | result_type operator()(const argument_type &e) const 1269 | { 1270 | size_t h{2166136261U}; 1271 | 1272 | for (uint32_t i{0}; i != e.len; ++i) 1273 | h = (h * 16777619) ^ e.p[i]; 1274 | 1275 | return h; 1276 | } 1277 | }; 1278 | } 1279 | #endif 1280 | 1281 | template 1282 | [[gnu::always_inline]] inline static constexpr int32_t TrivialCmp(const T &a, const T &b) 1283 | { 1284 | // Ref: http://stackoverflow.com/questions/10996418/efficient-integer-compare-function 1285 | return (a > b) - (a < b); 1286 | } 1287 | 1288 | [[gnu::always_inline]] inline int32_t constexpr TrivialCmp(const uint8_t a, const uint8_t b) 1289 | { 1290 | return a - b; 1291 | } 1292 | 1293 | [[gnu::always_inline]] inline int32_t constexpr TrivialCmp(const int8_t a, const int8_t b) 1294 | { 1295 | return a - b; 1296 | } 1297 | 1298 | [[gnu::always_inline]] inline int32_t constexpr TrivialCmp(const uint16_t a, const uint16_t b) 1299 | { 1300 | return a - b; 1301 | } 1302 | 1303 | [[gnu::always_inline]] inline int32_t constexpr TrivialCmp(const int16_t a, const int16_t b) 1304 | { 1305 | return a - b; 1306 | } 1307 | 1308 | [[gnu::always_inline]] inline int32_t constexpr TrivialCmp(const uint32_t a, const uint32_t b) 1309 | { 1310 | return (a > b) - (a < b); 1311 | } 1312 | 1313 | [[gnu::always_inline]] inline int32_t constexpr TrivialCmp(const int32_t a, const int32_t b) 1314 | { 1315 | return a - b; 1316 | } 1317 | 1318 | [[gnu::always_inline]] inline int32_t constexpr TrivialCmp(const uint64_t a, const uint64_t b) 1319 | { 1320 | return (a > b) - (a < b); 1321 | } 1322 | 1323 | [[gnu::always_inline]] inline int32_t constexpr TrivialCmp(const int64_t a, const int64_t b) 1324 | { 1325 | return (a > b) - (a < b); 1326 | } 1327 | --------------------------------------------------------------------------------