Is __int128 Integral? A Survey
quuxplusone.github.io
quuxplusone.github.io
The bigger problem with it is you can't just load/store a 128 bit value without this instruction. An SSE 128bit load/store is turned into two 64 bit operations under the hood. So to actually get atomic loads and stores you need to use this very expensive compare exchange operation. That really limits the number of algorithms it's useful for.
If you can do that, for most algorithms it doesn't matter much if you do two 64 bit loads to get the values. If you are out of sync your compare and swap is going to fail anyway, which is likely to be rare.
Maybe my experience is not representative, but it's something to be aware of when designing algorithms with it.
There are many better ways to do something like that. Anything that writes a buffer, then makes it available atomically would work much better, or you could just use the compare and swap.
There is always going to be a limit of what is atomic. At some point you have to work with a data structure meant for larger data. The cut off has to be somewhere.
Whatever else was part of your 128 bit struct could probably be part of the record instead. You could use a 64 bit address and a 64 bit version number. Looking up could be done by atomically reading the 64 bit version number only. Writing a new record would then require the compare and swap.
There are other ways of doing that though, I'm not sure the trade off was the best one in the end.
If you were worried about cache and indirection, the indices could be aligned to the start of a 64 byte or 128 byte boundary, since 64 bytes is the cache line size but (Intel) cpus will load two cache lines at a time. Because of this, loading in your 64 bit/8 byte index should mean that the 120 bytes after it are going to be cached.
Looks like I need to resort to ASM for visual c++. Might do.
None of these (as far as I know) can actually do a 128 bit compare and swap when it straddles two cache lines however.
These also aren't the same as doing actual 128 bit math operations, they are just for compare and swap.
It's not like it's even some new or radical idea - something like that was the original source of the C syntax for ints. To remind, the actual type names are not "short", "int", and "long", but rather "short int", "int", and "long int" - that is, "short" and "long" are qualifiers, just like "signed" and "unsigned"; it's just that you can omit "int". This all comes from ALGOL 68, where that scheme was explicitly generic - the language spec allowed for arbitrarily many SHORT and LONG before INT (or REAL). Implementations were then expected to use as many as they needed to express the types available on the architecture they were targeting.
Or better yet, the way Ada did it:
x: Integer;
y: Integer range 1 .. 1000;
z: Float digits 10> In addition to the integer types above, arbitrary bit-width integers can be referenced by using an identifier of i or u followed by digits. For example, the identifier i7 refers to a signed 7-bit integer. The maximum allowed bit-width of an integer type is 65535
I'm not sure if GCC had a 128-bit type before 64-bit ABIs were defined, but IIRC its 128-bit type didn't properly support all arithmetic operations until long after modern 64-bit ABIs, including the amd64 ABI, were finalized, so it wouldn't have been possible to specify a 128-bit intmax_t at the time.
There are several proposed amendments to the C standard to address the situation, either by deprecating intmax_t or changing its definition and requirements.
By contrast, the long long type (64-bit minimum width) was added to 32-bit ABIs without a problem because GCC and other compilers added long long support years before intmax_t was introduced with the C99 standard. If intmax_t was already a thing then adding long long to 32-bit ABIs would have been problematic, although there may have been more motivation to deal with the issue in the C standard.