- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
template <typename T, typename OUT_T = uint8_t>
OUT_T subdecoder_nbt::extract_bits(T bits, uint8_t pos, uint8_t end)
{
auto invert_bytes = [](T bytes) -> T
{
auto *p = reinterpret_cast<uint8_t*>(&bytes),
*p_end = reinterpret_cast<uint8_t*>(&bytes) + sizeof(bytes) - 1;
for(; p < p_end; ++p, --p_end)
{
*p = *p ^ *p_end;
*p_end = *p ^ *p_end;
*p = *p ^ *p_end;
}
return bytes;
};
bits = invert_bytes(bits);
bits <<= pos;
bits >>= (sizeof(bits) * 8 - (end - pos) - 1);
return (OUT_T)bits;
}
Как правильно доставать биты из промежутка из стандартных типов C++ на x86.
Изучал эту проблему в сумме почти сутки.
А всё потому, что x86 хранит байты в Little-Endian, из-за чего при сдвиге биты окажутся не там, где ожидаешь.
З.Ы. invert_bytes аля htons/htonl/bswap_64 обычно делают сразу после чтения, до любой работы с числом, а не посреди неё.
P.S. ты отредактировал комментарий, там был read
будет какое-то такое описание:
Можно потом из этого XML генерить протобуфную питушню какую-нибудь
Дык у протобуфа парсер в принципе не пригоден для кастомных форматов. Ибо там что-то в духе тегированных полей. Или что-то изменилось?
Ну что-то своё можно сгенерить. На крестах в общем-то просто имён полей хватает для генерации, остальное в компайлтайме доступно.
read() я убрал потому что я обосрался с проверкой его результата (signed-unsigned mismatch).
то окажется, что не инвертировав байты ты достанешь не оттуда.
там вообще в этот отрезок войдет еще один отрезок, который ему не должен принадлежать.
15 14 13 [12] 11 10 9 8 | [7] 6 5 4 3 2 1 0 | LE
~~~~~~~~ -------------------------- ~~~~~~~~~~~
[7] 6 5 4 3 2 1 0 | 15 14 13 [12] 11 10 9 8 | BE
--- ~~~~~~~~~~~~~~~~~~~~ --------------------
a ^= b ^= a ^= b;
Сейас прилетит питух, и скажет, что это UB
Или не скажет, а потом у тебя это в продакшене навернётся.
На самом деле я хз как устроен std::swap для стандартных типов.
Используется ли там xor-обмен.
xor-обмен никто в здравом уме не юзает, разве что выебнуться. Впрочем, компиляторы его умеют оптимизировать в один xchg. Как и классическую перестановку через временную переменную, которая юзается в дефолтном std::swap.
> Instructions with a LOCK prefix have a long latency that depends on cache organization and possibly RAM speed. If there are multiple processors or cores or direct memory access (DMA) devices, then all locked instructions will lock a cache line for exclusive access, which may involve RAM ac-cess. A LOCK prefix typically costs more than a hundred clock cycles, even on single-processor systems. This also applies to the XCHG instruction with a memory operand.
Ну это уже неправда вроде. Если нет реальной драки за кешлайн, то атомики относительно дешёвые.
Зачем? Зачем? Даже при обычном xchg, а не lock xchg у которого гарантии?
З.Ы. Бля, и правда. If a memory operand is referenced, the processor’s locking protocol is automatically implemented for the duration of the exchange operation, regardless of the presence or absence of the LOCK prefix or of the value of the IOPL.
И какой-нибудь флажок smp-aware в eflags, который отключает все автоблокировки.
Лол, в доке по оригинальному 8086 уже есть глава про Multiprocessing Features! И там xchg ещё не брал lock автоматически.
The 8086 and 8088 are designed for the multiprocessing environment. They have built-in features that help solve the coordination problems that have discouraged multiprocessing system development in the past.
LOCK may be used in multiprocessing systems to coordinate access to a common resource.
It is possible for another processor to obtain the bus between these two cycles and to gain access to the partially-updated semaphore. This can be prevented by preceding the XCHG instruction with a LOCK prefix.
Видимо какие-то лалки уже успели обосраться за 4 года.
LOCK на двойке был привилегированной инструкцией! А мутексов в юзермоде то хотелось. Вот и прикрутили автолочку к XCHG.
80386 перестал проверять привилегии на LOCK. Но уже было поздно.
Такой вот очередной плевок в вечность.
The 80386 defines new exceptions that can occur even in systems designed for the 80286.
Exception #6 - invalid opcode
This exception can result from improper use of the LOCK instruction.
Т.е. совместимость - это святое, но иногда можно и забить )))
Despite the fact that Pentium 4, Intel Xeon, and P6 family processors support processor ordering, Intel does not guarantee that future processors will support this model.
Какой дисклеймер )))
Т.е. интел внезапно может дропнуть гарантии про реордеринг. Но что-то мне намекает, что юзеры после этого дропнут интел.
Насколько я помню, на x86 только read вперёд write может проскочить (если они в разные места, само собой). Всё остальное идёт строго как написано в коде. Ну смёржиться разве что могут если WB или WC режим.
11.2.2 Automatic Locking
In several instances, the processor itself initiates activity on the data
bus. To help ensure that such activities function correctly in
multiprocessor configurations, the processor automatically asserts the LOCK#
signal. These instances include:
• Acknowledging interrupts.
• Setting busy bit of TSS descriptor.
• Loading of descriptors.
• Updating page-table A and D bits.
• Executing XCHG instruction.
14.7 Differences From 8086
...
4. Value written by PUSH SP.
The 80386 pushes a different value on the stack for PUSH SP than the
8086/8088. The 80386 pushes the value of SP before SP is incremented
as part of the push operation; the 8086/8088 pushes the value of SP
after it is incremented. If the value pushed is important, replace
PUSH SP instructions with the following three instructions:
This code functions as the 8086/8088 PUSH SP instruction on the 80386.
Видимо, поэтому инструкцию PUSH SP почти нигде не встретишь: SP сначала кладут в какой-нибудь регистр, потом пушат.
Хак для определения процессора (когда-то не было CPUID):
CPUID can be executed at any privilege level to serialize instruction execution. Serializing instruction execution guarantees that any modifications to flags, registers, and memory for previous instructions are completed before the next instruction is fetched and executed.
Х.з., на самом деле это единственная инструкция, которая сериализует исполнение в юзермоде.
Формально ещё можно iret дёрнуть, но ты же не будешь это делать посреди кода.
cpuid - самый сильный из заборов, доступных юзермоду. Остальные заборы что-то да разрешают переставлять.
По-моему это только для замера пирфоманса может пригодиться.
Я сейчас открыл доку по первому пню, где она изначально появилась. И она там уже сериализует исполнение.
The CPUID instruction can be executed at any privilege level to serialize instruction execution.
486 мог быть от 25 МГц до 80(2×40) МГц или 100(3×33) МГц.
386 мог быть от 16 МГц до 40 МГц.
Т. е. можно было нарваться и на 40-мегагерцовую «трёшку», и на 25-мегагерцовую «четвёрку».
mov ss, ...
mov sp, ...
И наебнуться от прерывания между ними.
Это расширение «Проводника», чтобы файл можно было мышкой кинуть на окошко, в котором отображается FTP. Почему они упомянули «Exchange», я не понял.
Где-то там были и патчи ОС под кривые прикладные программы.
Да иногда просто не везёт. На той версии мастера, которая была перед мержом твоих изменений и запуском тестов всё было норм. Но за это время кто-то уже пролез в мастер. И всё сломалось.
Полную сериализацию коммитов никто в здравом уме делать не будет. Поэтому иногда shit happens.
Придётся тесты очень сильно поджимать по времени и вбрасывать кучу железа на их распараллеливание.
По-моему это не особо пригодно для большой команды.
И 100% багов это всё равно не поймает. Ну не сможешь ты всё-всё-всё протестировать в такой сериализованной модели.
Ну вопрос в том, насколько полная сериализация коммитов ловит больше багов, чем более слабая но оптимистичная. И насколько она дороже обходится по железу и усилиям программистов.
Если была пара поломок мастера в месяц, то может и хуй с ними?
З.Ы. Я не против прекоммит тестов, я против их полной сериализации.
З.Ы. Беру свои слова обратно. gcc -O2 не осилило xor-swap оптимизнуть. А вот с std::swap весь твой extract_bits<uint32_t, uint32_t>(n, 0, 31) свернулся в одну ассемблерную инструкцию bswap.
Ну и с наивным конпелятором, который не умеет в эту идиому, превращается в сраное говнище, которое не даёт процу распараллелить выполнение (в обычном swap'е по сути просто регистры заренеймятся).
Скастовать в тип подходящего размера который ксорить можно, и потом поксорить (точнее, сделать указатель на такой-то тип с поддержкой xor, и присвоить в такие указатели адреса на переменные с неподходящим для перексориванием типы, и потом уже поксорить). Обменять float-ы так вполне можно.
Сможешь это написать без ub'ов и короче наивного свапа?
Прощайте, оптимизации, мне будет вас не хватать. Хотя с char * в общем-то так же.
Впрочем, xor swap вообще говоря говно, https://godbolt.org/z/9rT6ax оптимизируется он плохо. Компилятор не может в принципе породить точно такой же код 1 в 1 как при обмене переменных через третью, т.к. если мы меняем переменную саму с собой, xor-swap ее занулит
Ну вот это и есть самое большое говнище в xor-swap. Всё прекрасно работает, а потом кто-нибудь передаёт туда уко-ко-козатели на одинаковые пельменные — и пиздец.
Ну можно, только strict aliasing не забудь отключить.
Это ты про Эльбрус?
uint16_t __builtin_bswap16 (uint16_t x)
uint32_t __builtin_bswap32 (uint32_t x)
uint64_t __builtin_bswap64 (uint64_t x)
uint128_t __builtin_bswap128 (uint128_t x)
из GCC, но Clang их вроде тоже умеет.
У MSVC есть под это своя елда:
Еще есть интелевые интринсики:
https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_bswap
И более-менее стандартный htonl() ntohl() (В стандарте Си нет, но в POSIX есть).
В крестоговно почему-то не завезли никакой специальной хуйни для разворота байтиков, но зато там можно функцию Бесселя считать.
Ну это не совсем так, в последнем стандарте C++ описывается, что представление signed чисел может быть только в "two's complement" (дополнительный код). http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r4.html
И если это не описываеся, почему б это не описать тогда?
С другой стороны, функции которые ты привёл - это такая же чистая математика как и функции бесселя. Можно и добавить, даже если на какой-нибудь платформе в духе PDP они не имеют никакого смысла.
З.Ы. А потом захочется вращения чисел и поиска старшего бита.
Нет, пожалуй это ближе к области байтоебства, чем к математике. Зачем математикам разворачивать байтики?