aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Stoelp <johannes.stoelp@gmail.com>2023-07-02 22:33:19 +0200
committerJohannes Stoelp <johannes.stoelp@gmail.com>2023-07-02 22:33:19 +0200
commitd9743696562fc85643e016d778ee994f0b9296db (patch)
tree4cf08da0eb8569e227a412a6da45d1c476ffff78
downloadcpp-utils-d9743696562fc85643e016d778ee994f0b9296db.tar.gz
cpp-utils-d9743696562fc85643e016d778ee994f0b9296db.zip
add initial state of bitfield and option
-rw-r--r--.clang-format2
-rw-r--r--.gitignore4
-rw-r--r--Makefile36
-rw-r--r--bitfield.h193
-rw-r--r--option.h130
-rw-r--r--test/bitfield.cc59
-rw-r--r--test/option.cc89
7 files changed, 513 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..2eaa0eb
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,2 @@
+BasedOnStyle: Chromium
+AllowShortFunctionsOnASingleLine: Empty
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..13ca83a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+compile_commands.json
+events.json
+/build
+.cache/
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3f0706e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,36 @@
+TEST += bitfield
+TEST += option
+
+# -- INTERNALS -----------------------------------------------------------------
+
+BINS = $(TEST:%=build/%)
+DEPS = $(TEST:%=build/%.d)
+
+# -- FLAGS ---------------------------------------------------------------------
+
+DEPS_GEN = -MMD
+CXXFLAGS = -g -Wall -Wextra -Werror -I. -std=c++14 $(DEPS_GEN)
+
+# -- RULES ---------------------------------------------------------------------
+
+all: build $(BINS)
+
+bear:
+ bear intercept -- $(MAKE) all
+ bear citnames
+
+build/%: build/%.o
+ $(CXX) -o $@ $<
+
+build/%.o: test/%.cc
+ $(CXX) -c -o $@ $< $(CXXFLAGS)
+
+build:
+ mkdir -p build
+
+clean:
+ $(RM) -r build
+ $(RM) compile_commands.json events.json
+
+# Since DEPS files contain rules, include at the end.
+-include $(DEPS)
diff --git a/bitfield.h b/bitfield.h
new file mode 100644
index 0000000..833a722
--- /dev/null
+++ b/bitfield.h
@@ -0,0 +1,193 @@
+#ifndef UTILS_BITFIELD_H
+#define UTILS_BITFIELD_H
+
+#include <cstdint>
+#include <type_traits>
+
+namespace bitfield {
+namespace impl {
+/**
+ * Constant check for supported underlying BITFILED types.
+ */
+template <typename ValueType>
+static constexpr bool is_bitfield_type =
+ std::is_integral<ValueType>::value && std::is_unsigned<ValueType>::value &&
+ !std::is_same<ValueType, bool>::value;
+
+/**
+ * Compute a BITMASK based on the HIGHBIT and LOWBIT template parameters.
+ * The HIGHBIT and LOWBIT are inclusive.
+ *
+ * # Example
+ * ComputeMask<4, 7, uint32_t>() -> 0xf0
+ */
+template <std::uint8_t LowBit, std::uint8_t HighBit, typename ValueType>
+static constexpr inline ValueType compute_mask() {
+ using unsigned_t = typename std::make_unsigned<ValueType>::type;
+
+ constexpr unsigned_t kMaxBits = sizeof(unsigned_t) * 8;
+ static_assert(HighBit < kMaxBits, "HighBit exceeds bits of ValueType");
+ static_assert(LowBit <= HighBit, "HighBit must not be larger than LowBit");
+
+ constexpr unsigned_t kLen = HighBit - LowBit;
+ return kLen == (kMaxBits - 1)
+ ? ~static_cast<unsigned_t>(0)
+ : ((static_cast<unsigned_t>(1) << (HighBit - LowBit + 1)) - 1)
+ << LowBit;
+}
+} // namespace impl
+
+// -- FIELD REF ----------------------------------------------------------------
+
+/**
+ * A mutable reference to a single FIELD in a BITFIELD.
+ */
+template <std::uint8_t LowBit, std::uint8_t HighBit, typename ValueType>
+struct field_ref {
+ constexpr explicit field_ref(ValueType& val) : m_val{val} {}
+
+ constexpr operator ValueType() const {
+ return (m_val & kMask) >> LowBit;
+ }
+
+ constexpr ValueType val() const {
+ return static_cast<ValueType>(*this);
+ }
+
+ constexpr field_ref& operator=(ValueType val) {
+ m_val &= ~kMask;
+ return operator|=(val);
+ }
+
+ constexpr field_ref& operator|=(ValueType val) {
+ m_val |= (val << LowBit) & kMask;
+ return *this;
+ }
+
+ constexpr field_ref& operator&=(ValueType val) {
+ m_val &= (val << LowBit) & kMask;
+ return *this;
+ }
+
+ private:
+ ValueType& m_val;
+ enum : ValueType {
+ kMask = impl::compute_mask<LowBit, HighBit, ValueType>(),
+ };
+};
+
+// -- CONST FIELD REF ----------------------------------------------------------
+
+/**
+ * A constant reference to a single FIELD in a BITFIELD.
+ */
+template <std::uint8_t LowBit, std::uint8_t HighBit, typename ValueType>
+struct const_field_ref {
+ constexpr explicit const_field_ref(const ValueType& val) : m_val{val} {}
+
+ constexpr operator ValueType() const {
+ return (m_val & kMask) >> LowBit;
+ }
+
+ constexpr ValueType val() const {
+ return static_cast<ValueType>(*this);
+ }
+
+ private:
+ const ValueType& m_val;
+ enum : ValueType {
+ kMask = impl::compute_mask<LowBit, HighBit, ValueType>(),
+ };
+};
+
+// -- BITFIELD -----------------------------------------------------------------
+
+/**
+ * The BITFIELD base class.
+ */
+template <typename ValueType = std::uint32_t>
+struct bitfield {
+ static_assert(impl::is_bitfield_type<ValueType>,
+ "bitfield instantiated with incorrect type");
+
+ constexpr explicit bitfield(ValueType val) : m_val{val} {}
+
+ constexpr operator ValueType() const {
+ return m_val;
+ }
+
+ constexpr ValueType val() const {
+ return static_cast<ValueType>(*this);
+ }
+
+#define OPERATOR(OP) \
+ constexpr bitfield<ValueType>& operator OP(ValueType val) { \
+ m_val OP val; \
+ return *this; \
+ }
+ OPERATOR(=)
+ OPERATOR(|=)
+ OPERATOR(&=)
+#undef OPERATOR
+
+ protected:
+ ValueType m_val;
+};
+
+// -- MACROS -------------------------------------------------------------------
+//
+// Code generator macros to conveniently define BITFIELDs with multiple FIELDs.
+//
+// # Example
+//
+// BITFIELD_START(status_reg, std::uint32_t)
+// FIELD(N, 0, 0)
+// FIELD(V, 1, 1)
+// FIELD(C, 2, 2)
+// FIELD(Z, 3, 3)
+// FIELD(MODE, 4, 7)
+// BITFIELD_END()
+//
+// status_ref r(0);
+//
+// std::uint32_t n = r.V();
+// r.V() = 0xfff;
+// assert(r.V() == 0x1);
+// assert(r == 0x2);
+
+#define BITFIELD_START(NAME, TYPE) \
+ struct NAME : public bitfield::bitfield<TYPE> { \
+ using ValueType = TYPE; \
+ using bitfield<ValueType>::operator=; \
+ \
+ constexpr explicit NAME(ValueType val) : bitfield<ValueType>{val} {}
+
+#define BITFIELD_END() \
+ } \
+ ;
+
+#define FIELD(NAME, L, H) \
+ constexpr ::bitfield::field_ref<L, H, ValueType> NAME() { \
+ return ::bitfield::field_ref<L, H, ValueType>(m_val); \
+ } \
+ \
+ constexpr ::bitfield::const_field_ref<L, H, ValueType> NAME() const { \
+ return ::bitfield::const_field_ref<L, H, ValueType>(m_val); \
+ }
+
+// -- TESTS --------------------------------------------------------------------
+
+static_assert(impl::is_bitfield_type<std::uint32_t>, "");
+static_assert(!impl::is_bitfield_type<bool>, "");
+static_assert(!impl::is_bitfield_type<std::int32_t>, "");
+static_assert(!impl::is_bitfield_type<std::uint32_t*>, "");
+static_assert(!impl::is_bitfield_type<float>, "");
+
+static_assert(impl::compute_mask<0, 0, std::uint32_t>() == 0x1, "");
+static_assert(impl::compute_mask<4, 7, std::uint32_t>() == 0xf0, "");
+static_assert(impl::compute_mask<12, 12, std::uint32_t>() == 0x1000, "");
+static_assert(impl::compute_mask<15, 16, std::uint32_t>() == 0x18000, "");
+static_assert(impl::compute_mask<0, 31, std::uint32_t>() == 0xffffffff, "");
+
+} // namespace bitfield
+#endif
diff --git a/option.h b/option.h
new file mode 100644
index 0000000..b9ebe2f
--- /dev/null
+++ b/option.h
@@ -0,0 +1,130 @@
+#include <cassert>
+#include <new> // placement new
+#include <type_traits>
+#include <utility> // move
+
+namespace option {
+namespace impl {
+/// Definition of std::is_trivially_destructible_v for older c++ std versions.
+template <typename T>
+constexpr bool is_trivially_destructible_v =
+ std::is_trivially_destructible<T>::value;
+
+/// Definition of std::enable_if_t for older c++ std versions.
+template <bool Cond, typename T = bool>
+using enable_if_t = typename std::enable_if<Cond, T>::type;
+} // namespace impl
+
+/// The NONE type.
+struct none {};
+
+// The OPTION type.
+template <typename T>
+struct option {
+ static_assert(!std::is_reference<T>::value, "T must not be a reference type");
+
+ // -- CONSTRUCTOR - NONE -----------------------------------------------------
+
+ constexpr option() = default;
+ constexpr option(none) : option() {}
+
+ // -- CONSTRUCTOR - VALUE ----------------------------------------------------
+
+ constexpr option(const option& op) : m_has_value{op.has_value()} {
+ if (op.has_value()) {
+ // Copy construct from inner VALUE of OP.
+ new (m_value) T(op.value());
+ }
+ }
+
+ constexpr option(option&& op) : m_has_value{op.has_value()} {
+ if (op.m_has_value) {
+ // Move construct from inner VALUE of OP.
+ new (m_value) T(std::move(op.take()));
+ }
+ }
+
+ template <typename U = T>
+ constexpr option(T&& val) : m_has_value{true} {
+ new (m_value) T(std::move(val));
+ }
+
+ // -- DESTRUCTOR -------------------------------------------------------------
+
+ ~option() {
+ reset();
+ }
+
+ // -- MODIFIER ---------------------------------------------------------------
+
+ template <typename... Params>
+ constexpr T& emplace(Params&&... params) {
+ reset();
+ new (m_value) T(std::forward<Params>(params)...);
+ m_has_value = true;
+ return value();
+ }
+
+ // -- OPERATOR ---------------------------------------------------------------
+
+ /// Conversion to BOOL, true iff option holds a VALUE.
+ ///
+ /// Marked as explicit to disable implicit conversion of option objects to
+ /// bool in the following case:
+ ///
+ /// if (opt1 == opt2) {
+ /// ...
+ /// }
+ constexpr explicit operator bool() const {
+ return has_value();
+ }
+
+ // -- ACCESSOR ---------------------------------------------------------------
+
+ constexpr bool has_value() const {
+ return m_has_value;
+ }
+
+ constexpr T& value() & {
+ assert(m_has_value);
+ return *reinterpret_cast<T*>(m_value);
+ }
+
+ constexpr const T& value() const& {
+ assert(m_has_value);
+ return *reinterpret_cast<const T*>(m_value);
+ }
+
+ constexpr T value() && {
+ return take();
+ }
+
+ constexpr T take() {
+ assert(m_has_value);
+ T val = std::move(value());
+ reset();
+ return val;
+ }
+
+ // -- INTERNAL ---------------------------------------------------------------
+
+ private:
+ template <typename U = T,
+ impl::enable_if_t<!impl::is_trivially_destructible_v<U>> = true>
+ constexpr void reset() {
+ if (m_has_value) {
+ value().~T();
+ m_has_value = false;
+ }
+ }
+
+ template <typename U = T,
+ impl::enable_if_t<impl::is_trivially_destructible_v<U>> = true>
+ constexpr void reset() {
+ m_has_value = false;
+ }
+
+ alignas(T) char m_value[sizeof(T)];
+ bool m_has_value{false};
+};
+} // namespace option
diff --git a/test/bitfield.cc b/test/bitfield.cc
new file mode 100644
index 0000000..77bb286
--- /dev/null
+++ b/test/bitfield.cc
@@ -0,0 +1,59 @@
+#include <bitfield.h>
+
+#include <cassert>
+#include <cstdio>
+
+BITFIELD_START(MOOSE, std::uint32_t)
+ FIELD(IO, 4, 7)
+ FIELD(EL, 16, 31)
+BITFIELD_END()
+
+int main() {
+ MOOSE mo(0xab);
+
+ auto io = mo.IO();
+ auto el = mo.EL();
+
+ assert(mo.val() == 0xab);
+ assert(io.val() == 0xa);
+ assert(el.val() == 0);
+
+ io = 0x0;
+ std::printf("reg %08x f %08x\n", mo.val(), io.val());
+ assert(mo.val() == 0x0b);
+ assert(io.val() == 0x0);
+ assert(el.val() == 0);
+
+ el = 0xfeef;
+ std::printf("reg %08x f %08x\n", mo.val(), el.val());
+ assert(mo.val() == 0xfeef000b);
+ assert(io.val() == 0x0);
+ assert(el.val() == 0xfeef);
+
+ mo = 0xcafeu;
+ assert(mo.val() == 0xcafe);
+ assert(io.val() == 0xf);
+ assert(el.val() == 0x0);
+
+ assert(mo.val() == 0xcafe);
+ mo |= 0xa00b0000;
+ assert(mo.val() == 0xa00bcafe);
+ assert(io.val() == 0xf);
+ assert(el.val() == 0xa00b);
+
+ assert(mo.val() == 0xa00bcafe);
+ mo &= 0xffff0000;
+ assert(mo.val() == 0xa00b0000);
+ assert(io.val() == 0x0);
+ assert(el.val() == 0xa00b);
+
+ el |= 0xc0;
+ assert(mo.val() == 0xa0cb0000);
+ assert(io.val() == 0x0);
+ assert(el.val() == 0xa0cb);
+
+ el &= 0xf000;
+ assert(mo.val() == 0xa0000000);
+ assert(io.val() == 0x0);
+ assert(el.val() == 0xa000);
+}
diff --git a/test/option.cc b/test/option.cc
new file mode 100644
index 0000000..8697089
--- /dev/null
+++ b/test/option.cc
@@ -0,0 +1,89 @@
+#include <option.h>
+#include <cstdlib>
+#include <cstdio>
+
+struct Checker {
+ static unsigned cnt;
+
+ Checker() {
+ ++cnt;
+ }
+ Checker(const Checker&) {
+ ++cnt;
+ }
+ Checker(Checker&&) {
+ ++cnt;
+ }
+ ~Checker() {
+ --cnt;
+ }
+};
+
+unsigned Checker::cnt = 0;
+
+int main() {
+ using option::option;
+
+ auto check_cnt = [](unsigned expect) {
+ if (expect != Checker::cnt) {
+ std::printf("Checker: expect=%u cnt=%u\n", expect, Checker::cnt);
+ std::abort();
+ }
+
+ };
+
+ {
+ option<int> o1;
+ option<int> o2{::option::none{}};
+
+ assert(!o1.has_value());
+ assert(!o2.has_value());
+ }
+
+ // Assume we start test with cnt=0.
+ check_cnt(0);
+
+ {
+ option<Checker> o1(Checker{});
+ // copy construct
+ option<Checker> o2 = o1;
+ // move construct
+ option<Checker> o3 = o1.take();
+
+ assert(!o1.has_value());
+ assert(o2.has_value());
+ assert(o3.has_value());
+
+ // move option
+ option<Checker> o4 = std::move(o2);
+
+ assert(!o2.has_value());
+ assert(o4.has_value());
+
+ // take reference to inner
+ auto x = o3.value();
+ // take ownership of inner
+ auto y = o4.take();
+
+ assert(!o1.has_value());
+ assert(!o2.has_value());
+ assert(o3.has_value());
+ assert(!o4.has_value());
+ }
+
+ // Expect to finish test with cnt=0.
+ check_cnt(0);
+
+ {
+ option<Checker> o1;
+ assert(!o1.has_value());
+
+ o1.emplace();
+ assert(o1.has_value());
+ }
+
+ // Expect to finish test with cnt=0.
+ check_cnt(0);
+
+ return 0;
+}