Is there a highly performant way to make a 56 bit integer?

删除回忆录丶 提交于 2021-02-11 14:15:10

问题


I am writing a virtual machine that has 8 bit opcodes, and one of the common types for the instructions has the 8-bit opcode, followed in memory by a 56 bit signed integer operand.

Originally, I was going to implement this instruction as follows:

struct machine_op {
   std::uint64_t opcode:8
   std::int64_t operand:56;
};

However, Ive heard no end of comments that using bitfields like this would not be portable, and in particular, it would not guarantee that the operand field would actually be in the right place in memory (ie, the first byte of memory of the struct).

What I then thought of doing was to create a 56-bit integer class, and modify the above structure to the following:

struct machine_op {
   char opcode;
   int56 operand;
};

This would, by virtue of being a standard layout structure, guarantee that the address of the operand is immediately after the opcode, as long as int56 was written in a way that did not have any alignment restrictions.

And to that end, I have the following class defined, whose purpose is to encapsulate a signed 56 bit integer that has no alignment restrictions:

class int56 {
public:
    struct data {
        unsigned char byte1;
        unsigned char byte2;
        unsigned char byte3;
        unsigned char byte4;
        unsigned char byte5;
        unsigned char byte6;
        unsigned char byte7;
    };

    struct big_endian {
        char sign;
        data value;
        inline big_endian() = default;

        inline constexpr big_endian(const data &v) : sign(v.byte1 & 0x80 ? 0xff : 0), value(v)
        {
        }
    };

    struct little_endian {
        data value;
        char sign;
        inline little_endian() = default;

        inline constexpr little_endian(const data &v) : value(v), sign(v.byte7 & 0x80 ? 0xff : 0)
        {
        }
    };

    union aligner {
        std::int64_t value;
        big_endian big;
        little_endian little;
    };
    inline int56() = default;

    inline constexpr int56(std::int64_t x) :
#if BYTE_ORDER == LITTLE_ENDIAN
    value((aligner{x}).little.value)
#else
    value((aligner{x}).big.value)
#endif
    {
    }

    inline constexpr operator std::int64_t() const 
    {
#if BYTE_ORDER == LITTLE_ENDIAN
        return (aligner{.little = little_endian(value)}).value;
#else
        return (aligner{.big = big_endian(value)}).value;
#endif
    }

private:
    data value;
};

This implementation would only work for either big endian or little endian machines, however... other bit representations of integers would fail. Notwithstanding, I can live with that limitation, but even when using high optimization compiler flags, I find that accessing this class has a significant performance penalty over using bitfields.

I am, by the way, always going to be targetting machines with at least a 64-bit architecture, so there is no problem accessing data that is 64 bits wide. I just cannot resort to hand assembly because in addition to making those functions no longer constexpr, I would have to write assembly for each supported cpu architecture, and I am only particularly fluent in one of them.

Is this therefore reasonably the best I can do, or is there a better way to set up this int56 class?

Or, is there some way that I can be sure to be able to use bitfields to do what I actually need?

Thanks in advance

来源:https://stackoverflow.com/questions/50780920/is-there-a-highly-performant-way-to-make-a-56-bit-integer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!