Что-то неожиданно понял, что всячески пытался избегать такого кода и никогда на такое не напарывался , а тут неожиданно понадобилось:
Как правильно скопировать [signed xxx] -> [unsigned xxx] и обратно ???
Сейчас ловлю UB если пытаюсь делать в лоб простым присваиванием. ВАЖНО!!! — код обязательно должен выполняться на этапе компиляции!
P.S. Если кому интересно, нужно это так как я делаю библиотеку для работы с числами, а контейнер там содержит беззнаковые целые. Используется MSVC C++17
Здравствуйте, Videoman, Вы писали:
V>Что-то неожиданно понял, что всячески пытался избегать такого кода и никогда на такое не напарывался , а тут неожиданно понадобилось: V>Как правильно скопировать [signed xxx] -> [unsigned xxx] и обратно ??? V>Сейчас ловлю UB если пытаюсь делать в лоб простым присваиванием. ВАЖНО!!! — код обязательно должен выполняться на этапе компиляции!
Здравствуйте, Videoman, Вы писали:
V>Что-то неожиданно понял, что всячески пытался избегать такого кода и никогда на такое не напарывался , а тут неожиданно понадобилось: V>Как правильно скопировать [signed xxx] -> [unsigned xxx] и обратно ???
Ну если прямо побитово, то std::bit_cast
V>Сейчас ловлю UB если пытаюсь делать в лоб простым присваиванием. ВАЖНО!!! — код обязательно должен выполняться на этапе компиляции!
Где ты там UB нашёл?
Преобразование signed → unsigned всегда определено.
Обратное преобразование unsigned → signed раньше было implementation-defined для непредставимых чисел, но в последней версии языка стало точно также определено через модульную арифметику.
То есть с учётом того, что тебе известна версия компилятора, для преобразования достаточно написать в теле функции return value и искать ошибки в других местах.
Здравствуйте, watchmaker, Вы писали:
W>Ну если прямо побитово, то std::bit_cast
Ну я вроде ж написал что С++17. Ну не поддерживает от memcpy в сonstexpr
W>Где ты там UB нашёл?
Я только догадываюсь, т.к. у меня в дебаге все работает, а в релизе вместо -1 оказывается 0.
W>Преобразование signed → unsigned всегда определено.
Т.е. -1 -> 0xffff....U — всегда по стандарту? Это точно?
W>Обратное преобразование unsigned → signed раньше было implementation-defined для не представимых чисел, но в последней версии языка стало точно также определено через модульную арифметику. W>То есть с учётом того, что тебе известна версия компилятора, для преобразования достаточно написать в теле функции return value и искать ошибки в других местах.
Я использую MSVC 2017. Там С++17. Думаю что до С++ 20, для знакомых типов, много чего не стандартизировано, поэтому спрашиваю на всякий случай знающих людей.
Здравствуйте, Videoman, Вы писали:
V>Там С++17.
Значит это implementation-defined.
Какая у тебя используется имплементация ты сам в этой же строчке написал: V>Я использую MSVC 2017.
Открываем описание и …
When signed variables are converted to unsigned and vice versa, the bit patterns remain the same.
V>Я только догадываюсь, т.к. у меня в дебаге все работает, а в релизе вместо -1 оказывается 0.
Попробуй тогда собрать минимальный пример, что-ли, где такое получается. Или W>> искать ошибки в других местах.
Здравствуйте, watchmaker, Вы писали:
W>Попробуй тогда собрать минимальный пример, что-ли, где такое получается.
Пробую. Это не так просто. Нужно перелопатить кучу кода. Буду пытаться. Пока, мне достаточно убедиться что signed = unsigned и наоборот, это не UВ, а implementation-defined и в большинстве распространенных компиляторов это простое побитовое копирование. Мне это важно знать что бы не смотреть всюду и сузить поиски ошибки. И все-таки UB я где-то рядом умудрился словить, в казалось бы, элементарном коде с простыми пересылками signed/unsigned из одного конструктора в другой .
W>Или искать ошибки в других местах.
И это тоже конечно буду делать.
V>По-моему битовый сдвиг (>>) в рамках С++17 не определен точно для знаковых типов, разве нет?
For negative a, the value of a >> b is implementation-defined (in most implementations, this performs arithmetic right shift, so that the result remains negative).
Другой варинат implementation-defined — логический сдвиг.
Но в данном случае влиять не должно, появившийся ниоткуда бит в итоге выпадает.
Т.е. для всех практически существующих реализаций будет одинаково.
V>>Сейчас ловлю UB если пытаюсь делать в лоб простым присваиванием. ВАЖНО!!! — код обязательно должен выполняться на этапе компиляции! W>Где ты там UB нашёл? W>Преобразование signed → unsigned всегда определено. W>Обратное преобразование unsigned → signed раньше было implementation-defined для непредставимых чисел, но в последней версии языка стало точно также определено через модульную арифметику. W>То есть с учётом того, что тебе известна версия компилятора
То нужно проверить, что там преобразование unsigned → signed для непредставимых чисел не UB.
implementation-defined обязывает реализацию документировать поведение, но не обязывает делать его определённым.
Удалось все выкинуть лишнее.
Вот такой код на С++17 в release x64 дает неожиданный для меня результат:
На godbolt я не могу найти 17-й компилятор. И там все работает.
Как-то более прямо надо передать ему 0, как char значение, а не uintptr_t ссылку. для первой итерации в идеале вообще как compile-time константу.
Я видел этот вопрос на SO, примерно то же предложил, не видя asm
Вообще сама генерация adc вместо add для первого шага, с махинациями с получением значения CF -- это уже пессимизация.
Компилятор должен использовать adc только для последующих шагов, где CF естественным образом получается из предыдущей инструкции.
И если не морочить его классами, ссылками и шаблонами, то он в состоянии. https://gcc.godbolt.org/z/arGuaM
Соотвественно, надо что-то лишнее исключить, и я бы таки со ссылок начал.
Да, вы правы. Я сам уже 2 дня смотрю на этот ассемблерный выхлоп и офигиваю как он заносит 0 в переменную: сначала 1, а потом прибавляет к ней -1 в итоге получая таки 0 но портя операцией add флаг переноса который из-за оборота вокруг нуля становится единицей. Эта единица используется как дополнительный член суммы при следующем adc. Я правильно уловил суть?
При чем тупит он так только если смешивать суммирование положительных и отрицательных значений. Почему у меня и возникло подозрения что я где-то на UB нарвался.
AG>Как-то более прямо надо передать ему 0, как char значение, а не uintptr_t ссылку. для первой итерации в идеале вообще как compile-time константу.
Ох, у меня на самом деле огромный класс и сигнатура была выбрана именно такой для того, что бы этот код корректно работал с обобщенной шаблонной версией. А в шаблонах туда могут передаваться не только целые, но и другие классы и т.д.
AG>Вообще сама генерация adc вместо add для первого шага, с махинациями с получением значения CF -- это уже пессимизация. AG>Компилятор должен использовать adc только для последующих шагов, где CF естественным образом получается из предыдущей инструкции.
Вообще, в более простых случаях он без проблем генерирует add вместо первого сложения. Проблема в том, что я физически не могу ему сказать делай первое сложение через add, т.к. я не нашел такого intrinsic. А иначе у меня не будет carry флага после первого сложения.
AG>И если не морочить его классами, ссылками и шаблонами, то он в состоянии. AG>https://gcc.godbolt.org/z/arGuaM AG>Соотвественно, надо что-то лишнее исключить, и я бы таки со ссылок начал.
Да, я сейчас пытаюсь изменить дизайн. Как получится отпишусь.
Здравствуйте, Videoman, Вы писали:
V>Я сам уже 2 дня смотрю на этот ассемблерный выхлоп и офигиваю как он заносит 0 в переменную: сначала 1, а потом прибавляет к ней -1 в итоге получая таки 0 но портя операцией add флаг переноса который из-за оборота вокруг нуля становится единицей. Эта единица используется как дополнительный член суммы при следующем adc. Я правильно уловил суть?
Да. И это не порча, а именно способ занесения. -1 следует понимать как беззнаковое, т.е. это 255 или 0xFF. То есть прибавить к 0 — carry flag не будет, к любому другому число — будет.
Для занесения CF = 1 есть инструкция STC, для сброса в 0 — CLC
Штука в том, что он хочет сделать это branchless — т.е. без условных переходов — т.к. бранчи, зависящие от данных могут неправильно предсказываться, и это часто приносит потери производительности.
Т.е. способ CLC / STC был бы хорош лишь для константы, при этом CLC+ADC можно выродить в ADD.
> addc(const uint64_t& value1, const uint64_t& value2, uint64_t& carry)
это очень плохая сигнатура, кроме того, что всё передайтся по ссылке, ссылка одного типа, поэтому компилятор может думать, что возможен aliasing, и генерирует безопасную дичь.
Просто замена uint64_t& carry на uint8_t& carry уже может улучшить ситуацию.
Здравствуйте, Alexander G, Вы писали:
>> addc(const uint64_t& value1, const uint64_t& value2, uint64_t& carry)
AG>это очень плохая сигнатура, кроме того, что всё передайтся по ссылке, ссылка одного типа, поэтому компилятор может думать, что возможен aliasing, и генерирует безопасную дичь. AG>Просто замена uint64_t& carry на uint8_t& carry уже может улучшить ситуацию.
Я знаю что дизайн не очень, но к сожалению с этим приходится мирится для очень низкоуровневых операций. Замена всех ссылок на передачу по значению приводит точно к такому же результату. На этом intrinsic сносит крышу (кстати, с вычитанием тоже самое происходит).
Т.е. ваша догадка насчет типа carry полностью подтвердилась. Компилятор клинит где-то на стыке carry -> _addcarry_u64. Все другие изменения (замена всех ссылок на значения с возвратом кортежей или структур и т.д.) приводили к полностью идентичному ассемблерному выходу.
Спасибо вам большое за помощь! Интересно как вы догадались на счет unsigned char ???
V> Интересно как вы догадались на счет unsigned char ???
0. Интринсик принимает байт этим параметром
1. Подозревал так алиасинг, отличный от uint64_t тип его исключает
2. В дизасме явно видна работа с байтами al или cl, не eax/rax
3. И вообще, если забыть про архаизмы вроде #define BOOL int и VARIANT_BOOL, то булки — байты
Здравствуйте, Alexander G, Вы писали:
AG>0. Интринсик принимает байт этим параметром AG>1. Подозревал так алиасинг, отличный от uint64_t тип его исключает AG>2. В дизасме явно видна работа с байтами al или cl, не eax/rax AG>3. И вообще, если забыть про архаизмы вроде #define BOOL int и VARIANT_BOOL, то булки — байты
Да, очень вероятно что где-то внутри компилятора carry не unsigned char, а bool, и где-то типы у них скастились один в другой. Ничего, зато поправил, все тесты прошли. Теперь могу писать такой код и все работает на уровне компиляции :
constexpr auto oct = 03766713523035452062041773345651416625031020_u128;
constexpr auto dec = 338770000845734292534325025077361652240_u128;
constexpr auto hex = 0xfedcba9876543210fedcba9876543210_u128;
Еще раз, спасибо большое за помощь! В этот раз проблема действительно оказалась не простой.