summaryrefslogtreecommitdiffstats
path: root/src/common/bit_field.h
blob: 368b7b98c92a6514133529d65b350e763f219027 (plain) (blame)
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
// SPDX-FileCopyrightText: 2014 Tony Wasserka
// SPDX-FileCopyrightText: 2014 Dolphin Emulator Project
// SPDX-License-Identifier: BSD-3-Clause AND GPL-2.0-or-later

#pragma once

#include <cstddef>
#include <limits>
#include <type_traits>
#include "common/swap.h"

/*
 * Abstract bitfield class
 *
 * Allows endianness-independent access to individual bitfields within some raw
 * integer value. The assembly generated by this class is identical to the
 * usage of raw bitfields, so it's a perfectly fine replacement.
 *
 * For BitField<X,Y,Z>, X is the distance of the bitfield to the LSB of the
 * raw value, Y is the length in bits of the bitfield. Z is an integer type
 * which determines the sign of the bitfield. Z must have the same size as the
 * raw integer.
 *
 *
 * General usage:
 *
 * Create a new union with the raw integer value as a member.
 * Then for each bitfield you want to expose, add a BitField member
 * in the union. The template parameters are the bit offset and the number
 * of desired bits.
 *
 * Changes in the bitfield members will then get reflected in the raw integer
 * value and vice-versa.
 *
 *
 * Sample usage:
 *
 * union SomeRegister
 * {
 *     u32 hex;
 *
 *     BitField<0,7,u32> first_seven_bits;     // unsigned
 *     BitField<7,8,u32> next_eight_bits;      // unsigned
 *     BitField<3,15,s32> some_signed_fields;  // signed
 * };
 *
 * This is equivalent to the little-endian specific code:
 *
 * union SomeRegister
 * {
 *     u32 hex;
 *
 *     struct
 *     {
 *         u32 first_seven_bits : 7;
 *         u32 next_eight_bits : 8;
 *     };
 *     struct
 *     {
 *         u32 : 3; // padding
 *         s32 some_signed_fields : 15;
 *     };
 * };
 *
 *
 * Caveats:
 *
 * 1)
 * BitField provides automatic casting from and to the storage type where
 * appropriate. However, when using non-typesafe functions like printf, an
 * explicit cast must be performed on the BitField object to make sure it gets
 * passed correctly, e.g.:
 * printf("Value: %d", (s32)some_register.some_signed_fields);
 *
 * 2)
 * Not really a caveat, but potentially irritating: This class is used in some
 * packed structures that do not guarantee proper alignment. Therefore we have
 * to use #pragma pack here not to pack the members of the class, but instead
 * to break GCC's assumption that the members of the class are aligned on
 * sizeof(StorageType).
 * TODO(neobrain): Confirm that this is a proper fix and not just masking
 * symptoms.
 */
#pragma pack(1)
template <std::size_t Position, std::size_t Bits, typename T, typename EndianTag = LETag>
struct BitField {
private:
    // UnderlyingType is T for non-enum types and the underlying type of T if
    // T is an enumeration. Note that T is wrapped within an enable_if in the
    // former case to workaround compile errors which arise when using
    // std::underlying_type<T>::type directly.
    using UnderlyingType = typename std::conditional_t<std::is_enum_v<T>, std::underlying_type<T>,
                                                       std::enable_if<true, T>>::type;

    // We store the value as the unsigned type to avoid undefined behaviour on value shifting
    using StorageType = std::make_unsigned_t<UnderlyingType>;

    using StorageTypeWithEndian = typename AddEndian<StorageType, EndianTag>::type;

public:
    /// Constants to allow limited introspection of fields if needed
    static constexpr std::size_t position = Position;
    static constexpr std::size_t bits = Bits;
    static constexpr StorageType mask = (((StorageType)~0) >> (8 * sizeof(T) - bits)) << position;

    /**
     * Formats a value by masking and shifting it according to the field parameters. A value
     * containing several bitfields can be assembled by formatting each of their values and ORing
     * the results together.
     */
    [[nodiscard]] static constexpr StorageType FormatValue(const T& value) {
        return (static_cast<StorageType>(value) << position) & mask;
    }

    /**
     * Extracts a value from the passed storage. In most situations prefer use the member functions
     * (such as Value() or operator T), but this can be used to extract a value from a bitfield
     * union in a constexpr context.
     */
    [[nodiscard]] static constexpr T ExtractValue(const StorageType& storage) {
        if constexpr (std::numeric_limits<UnderlyingType>::is_signed) {
            std::size_t shift = 8 * sizeof(T) - bits;
            return static_cast<T>(static_cast<UnderlyingType>(storage << (shift - position)) >>
                                  shift);
        } else {
            return static_cast<T>((storage & mask) >> position);
        }
    }

    BitField(T val) {
        Assign(val);
    }

    BitField& operator=(T val) {
        Assign(val);
        return *this;
    }

    constexpr BitField() noexcept = default;

    constexpr BitField(const BitField&) noexcept = default;
    constexpr BitField& operator=(const BitField&) noexcept = default;

    constexpr BitField(BitField&&) noexcept = default;
    constexpr BitField& operator=(BitField&&) noexcept = default;

    [[nodiscard]] constexpr operator T() const {
        return Value();
    }

    constexpr void Assign(const T& value) {
#ifdef _MSC_VER
        storage = static_cast<StorageType>((storage & ~mask) | FormatValue(value));
#else
        // Explicitly reload with memcpy to avoid compiler aliasing quirks
        // regarding optimization: GCC/Clang clobber chained stores to
        // different bitfields in the same struct with the last value.
        StorageTypeWithEndian storage_;
        std::memcpy(&storage_, &storage, sizeof(storage_));
        storage = static_cast<StorageType>((storage_ & ~mask) | FormatValue(value));
#endif
    }

    [[nodiscard]] constexpr T Value() const {
        return ExtractValue(storage);
    }

    [[nodiscard]] constexpr explicit operator bool() const {
        return Value() != 0;
    }

private:
    StorageTypeWithEndian storage;

    static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range");

    // And, you know, just in case people specify something stupid like bits=position=0x80000000
    static_assert(position < 8 * sizeof(T), "Invalid position");
    static_assert(bits <= 8 * sizeof(T), "Invalid number of bits");
    static_assert(bits > 0, "Invalid number of bits");
    static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable in a BitField");
};
#pragma pack()

template <std::size_t Position, std::size_t Bits, typename T>
using BitFieldBE = BitField<Position, Bits, T, BETag>;