Welcome to mirror list, hosted at ThFree Co, Russian Federation.

bit_view.h « include - github.com/dosbox-staging/dosbox-staging.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: d2af776f67afc6b6122d7fe972b9e6ebe32e3271 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/*
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *
 *  Copyright (C) 2022-2022  The DOSBox Staging Team
 *
 *  Thanks to Evan Teran for his public domain BitField class, on which
 *  this is based.
 *
 *  This program is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free Software
 *  Foundation; either version 2 of the License, or (at your option) any later
 *  version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 *  this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
 *  Street, Fifth Floor, Boston, MA 02110-1301, USA.

bit_view
~~~~~~~~

The bit_view class is a wrapper around an 8-bit unsigned integer union member
that offers named access to one or more spans of bits.

For example:

union Register {
        uint8_t data = 0;
        bit_view<0, 1> first_bit;     // value is 0 or 1
        bit_view<1, 6> middle_6_bits; // value is 0 to 2^6-1
        bit_view<7, 1> last_bit;      // value is 0 or 1
};

It provides a view into a subset of its bits allowing them to be read,
written, assigned, flipped, cleared, and tested, without the need for
bit-twidling operations (such as shifting, masking, anding, or or'ing).

Constructing a bit_view is similar to C bitfields, however unlike C
bitfields, bit_views are free from undefined behavior and have been proven
using GCC's and Clang's undefined behavior sanitizers.

This gives us the benefits of bitfields without their specification downsides:
- they're succinct and clear to use
- they're just as fast as bitwise operators (maybe more so, being constexpr)
- they're self-documenting using their bit positions, sizes, and field names

Example usage
~~~~~~~~~~~~~

Assume we have a 8-bit audio control register that holds the card's:
- speaker-on state (off/on) starting at bit 0, using one bit
- stereo-output state (off/on) starting at bit 1, using one bit
- left channel pan starting at bit 2, using 3 bits
- right channel pan starting at bit 5, using 3 bits

bit_view's can make this register's elements self-documenting:

union AudioReg {
  uint8_t data = 0;
  bit_view<0, 1> speaker_on;
  bit_view<1, 1> stereo_output;
  bit_view<2, 3> left_pan;
  bit_view<5, 3> right_pan;
};

AudioReg reg = {data};

if (reg.speaker_on)
  enable_speaker();
else
  disable_speaker();

if (reg.stereo_output)
  stereo_pan(reg.left_pan, reg.right_pan)

8-bit limitation
~~~~~~~~~~~~~~~~

bit_views are endian-safe as they're limited wrapping 8-bit registers,
which are not affected by the byte-ordering of larger multi-byte type.
(compile-type assertions guarantee this as well).

To use bit_views with larger types or registers, the type should be
accessible in it's explicit 8-bit parts (for example, a uint32_t can be
represented as an array of four uin8_t), each element of which can be
assigned to a bit_view-based union.

This is deliberate because the byte-ordering of the bit-view's data cannot
be assumed. For example, the byte ordering of a 16-bit register populated
within a DOS emulator will be little-endian, even when running on a
big-endian host, where as native data types on big-endian hardware will be
big-endian.
*/

#ifndef BIT_VIEW_H_
#define BIT_VIEW_H_

#include <cassert>
#include <type_traits>

#include "bitops.h"
#include "support.h"

template <int view_index, int view_width>
class bit_view {
private:
	// ensure the data type is large enough to hold the view
	using data_type = nearest_uint_t<view_index + view_width>;

	// compile-time assert that the view is valid
	static_assert(std::is_unsigned<data_type>::value,
	              "the bit_view's data type needs to be unsigned");

	static_assert(view_index >= 0,
	              "the bit_view's index needs to be zero or greater");

	static_assert(view_width > 0,
	              "the bit_view's width needs to span at least one bit");

	static_assert(view_index + view_width <= std::numeric_limits<uint8_t>::digits,
	              "the bit_view's extents need to fit within an uint8_t data type");

	// ensure the right-hand-side is an integer and fits in the data type
	template <typename rhs_type>
	constexpr void check_rhs([[maybe_unused]] const rhs_type rhs_value) noexcept
	{
		// detect attempts to assign from non-integral types
		static_assert(std::is_integral<rhs_type>::value,
		              "the bit_view's value can only accept integral types");

		// detect use of bool, in which case the data is 1 bit and safe
		if (std::is_same<rhs_type, bool>::value)
			return;

		// detect assignments of negative values
		if (std::is_signed<rhs_type>::value)
			assert(rhs_value >= 0);

		// detect assignment of values that are too large
		[[maybe_unused]] constexpr uint64_t max_data_value = {0b1u << view_width};
		assert(static_cast<uint64_t>(rhs_value) < max_data_value);
	}

	// hold the view's masks using an enum to avoid using class storage
	enum mask : data_type {
		unshifted = (1u << view_width) - 1u,
		shifted   = unshifted << view_index,
	};

	// leave the member uninitialised; let union peer(s) initialize the data
	data_type data;

public:
	constexpr bit_view() = default;

	// trivial copy constructor
	bit_view(const bit_view &other) noexcept = default;

	// construct the view from a right-hand-side value
	template <class rhs_type>
	constexpr bit_view(const rhs_type rhs_value) noexcept
	{
		// use the = operator to save the value into the view
		*this = rhs_value;
	}

	// assign from right-hand-side boolean
	constexpr bit_view &operator=(const bool b) noexcept
	{
		constexpr uint8_t bool_to_val[2] = {0, 1};

		// use the = operator to save the value into the view
		return *this = bool_to_val[static_cast<size_t>(b)];
	}

	// assign from right-hand-side value
	template <class rhs_type>
	constexpr bit_view &operator=(const rhs_type rhs_value) noexcept
	{
		check_rhs(rhs_value);
		const auto outer = data & ~mask::shifted;
		const auto inner = (rhs_value & mask::unshifted) << view_index;

		data = static_cast<data_type>(outer | inner);
		return *this;
	}

	// assign the view from another bit_view if the same type
	constexpr bit_view &operator=(const bit_view &other) noexcept
	{
		// get the other view's value using the data_type() operator
		const data_type d = other;

		// use the = operator to save the value into the view
		return *this = d;
	}

	// read the view's value
	constexpr operator data_type() const noexcept
	{
		return (data & mask::shifted) >> view_index;
	}

	// pre-increment the view's value
	constexpr bit_view &operator++() noexcept
	{
		// use the = operator to save the value into the view
		return *this = *this + 1;
	}

	// post-increment the view's value
	constexpr bit_view operator++(int) noexcept
	{
		const auto b = *this;
		++*this;
		return b;
	}

	// increment the view's value by the right-hand side
	template <class rhs_type>
	constexpr bit_view &operator+=(const rhs_type rhs_value) noexcept
	{
		check_rhs(rhs_value);
		// use the = operator to save the value into the view
		return *this = *this + rhs_value;
	}

	// pre-decrement the view's value
	constexpr bit_view &operator--() noexcept
	{
		// use the = operator to save the value into the view
		return *this = *this - 1;
	}

	// post-decrement the view's value
	constexpr bit_view operator--(int) noexcept
	{
		const auto b = *this;
		--*this;
		return b;
	}

	// decrement the view's value by the right-hand side
	template <class rhs_type>
	constexpr bit_view &operator-=(const rhs_type rhs_value) noexcept
	{
		check_rhs(rhs_value);
		return *this = *this - rhs_value;
	}

	// check if all the view's bits are set
	constexpr bool all() const noexcept
	{
		return bit::is(data, mask::shifted);
	}

	// check if any of the view's bits are set
	constexpr bool any() const noexcept
	{
		return bit::any(data, mask::shifted);
	}

	// check if none of the view's bits are set
	constexpr bool none() const noexcept
	{
		return bit::cleared(data, mask::shifted);
	}

	// expose the view's underlying data
	constexpr data_type get_data() const noexcept
	{
		return data & mask::shifted;
	}

	// flip the view's bits
	constexpr void flip() noexcept
	{
		bit::flip(data, mask::shifted);
	}

	// clear the view's bits
	constexpr void clear() noexcept
	{
		bit::clear(data, mask::shifted);
	}

	// get the numeric value of the view's bits
	constexpr data_type val() const noexcept
	{
		// use the view's value-cast operator to get the shifted and masked bits
		return static_cast<data_type>(*this);
	}
};

#endif