diff options
-rw-r--r-- | .clang-format | 2 | ||||
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 36 | ||||
-rw-r--r-- | bitfield.h | 193 | ||||
-rw-r--r-- | option.h | 130 | ||||
-rw-r--r-- | test/bitfield.cc | 59 | ||||
-rw-r--r-- | test/option.cc | 89 |
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; +} |