Re[28]: Carbon
От: CreatorCray  
Дата: 19.04.24 22:13
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Я прошёлся по куче компиляторов — даже когда is_max не уничтожается (подавал не константу компиляции) — эффект был тот же на -O2, всё те же oops для первоначального варианта. ))

Алгоритмические оптимизации делаются во фронтэнде. И если он решил что тут будет всегда false, потому что на абстрактное (x + 1) < x он смотрел без учёта размерностей, то бэкенд именно return false, как ему сказали и сгенерирует.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re[26]: Carbon
От: CreatorCray  
Дата: 19.04.24 22:13
Оценка: +1
Здравствуйте, vdimas, Вы писали:

V>Он пытается решить вопрос снижения пресловутой "планки входа".

А может не надо?
Толпа тупых своим нытьём и требованиями только портит язык для тех, кто умеет им пользоваться.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re[11]: Carbon
От: CreatorCray  
Дата: 19.04.24 22:13
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Но коллега-плюсовик там откровенно плавает, увы, чем дал тебе возможность власть порезвиться, ну ты и не преминул, чо!


Это КСВ, тут никто напрягаться не будет, заработало как надо — покажем, нафиг что то там ещё причёсывать?

Сам при этом

V>тоже допустил ошибку в одном из вариантов

... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re[29]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 20.04.24 05:28
Оценка:
Здравствуйте, CreatorCray, Вы писали:

CC>Здравствуйте, vdimas, Вы писали:


V>>Я прошёлся по куче компиляторов — даже когда is_max не уничтожается (подавал не константу компиляции) — эффект был тот же на -O2, всё те же oops для первоначального варианта. ))

CC>Алгоритмические оптимизации делаются во фронтэнде. И если он решил что тут будет всегда false, потому что на абстрактное (x + 1) < x он смотрел без учёта размерностей, то бэкенд именно return false, как ему сказали и сгенерирует.
В современных компиляторах — нет. В старом ICC это, наверное, было так — поэтому, в частности, вам удалось сбить его с толку введением временной переменной.
А сейчас так не делают — все вот эти алгебраические эквивалентности фронтендерам делать лень; у них и так хватает работы по разбору синтаксиса. Всё это делается уже потом, при анализе CFG в IR.
И никакого "без учёта размерностей" тут нету — ведь для unsigned типов этот код всегда компилируется совершенно корректно. То есть почему-то компилятор считает, что беззнаковый инкремент может уменьшить x, а вот знаковый "почему-то" не может. Вот как раз потому он так и считает, что для знакового инкремента переполнение — UB, а для беззнакового — нет.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[28]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 20.04.24 05:33
Оценка: +1 :))
Здравствуйте, vdimas, Вы писали:

V>Здравствуйте, Sinclair, Вы писали:

V>Почему конкретнос в span<> использутся знаковые индексы — потому что это смещения, для которых как раз знаковые и пригодны, о чём я сразу же и сказал, а ты побежал спорить от "недостаточной компетенции" (С) Синклер.
Напомню, что в статье речь не только об индексах, но и о size(), который ну никак-никак отрицательным быть не может.
Оттуда же пример про unsigned area(unsigned h, unsigned w).

V>Да ничего там не запрещено:

V>

V>In an unchecked context, overflows are ignored and any high-order bits that do not fit in the destination type are discarded.

Продолжаем отжиг в жанре "гляжу в книгу — вижу фигу".
Это и есть defined behavior.
Следите за руками: в дотнете прибавление единицы к int 0x7FFFFFFF даёт int 0x80000000 (overflows are ignored). Здесь нет никаких бит для отбрасывания, т.к. весь результат fits in the destination type.
А в C++ такое прибавление даёт undefined behavior.

V>"destination type" для переменной в памяти или возвращаемого значения метода это одно, а для промежуточного значения при вычислении формулы унутре регистров может быть другим.

Для интегральных типов — нет, не может.

V>В сегодняшних плюсах — это консенсус производителей процессоров и огромного сообщества разработчиков на С/С++, где размер сообщества значительно больше размера сообщества дотнетчиков.



V>Это не тебе решать.

Естественно. Просто это уже решено.

V>Моё "приведённое поведение" лишь демонстрирует суть проблемы (если кто еще не уловил, почему в твоей версии были oops) — часто в железе выгодней оперировать размером в слово, чем в полуслово с постоянной коррекцией результата или с вызовом инструкций с длинными префиксами, как это происходит для инструкций в полуслово для intel-архитектуры.

Зачем вы повторяете глупость? Расскажите мне, каким размером оперирует в железе is_max<long>? Каким размером оперирует is_max<unsigned long>?
И почему первая выдаёт oops, а вторая — wow?

V>Учитывая, что за десятки постов обсуждения с коллегой никто из вас так и не догадался, что происходит и почему именно так — я посчитал нужным продемонстрировать происходящее явным образом.

Все, кому надо, давно обо всём догадались. Один вы продолжаете тупить.
Если вас посетит нестерпимое желание продолжать, распишите по шагам, что там происходит за "расширение полуслова до слова" для int64, и что делается при "постоянной коррекции результата".
Возможно, вас посетит просветление. Хоть и вряд ли.

V>Со всеми тремя из их официальной книги об истории и ходе разработки дотнета.

V>Уже не в первый раз тебе рекомендую, а то витаешь в облаках, несёшь дичь какую-то из пальца насосанную.
Давайте что ли ссылку на книгу. А то ваша способность читать то, чего нету, и не читать то, что есть, достигла легендарного уровня.

V>И чем там поможет знаковое в сравнении с беззнаковым?

Тем, что по условию, входные числа — знаковые.
V>И чем там поможет дотнет в режиме unchecked?
V>Там тупая потеря результата, если не удаётся расширить промежуточное значение (я же тебе же об этом же и отвечал ).
Ну, так вопрос-то как раз в том, как этой потери избежать. Результат там совершенно точно влазит в диапазон long, не так ли

Вот, скажем, std::midpoint — канонический пример, где лучшие специалисты планеты смогли найти ответ только с третьей попытки. И это — совсем недавнее прошлое. Вряд ли там дотнетчики виноваты.
Они вообще не в курсе всей драмы с midpoint и неожиданными UB на ровном месте.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[29]: Carbon
От: vdimas Россия  
Дата: 20.04.24 07:45
Оценка: :)
Здравствуйте, CreatorCray, Вы писали:

V>>Я прошёлся по куче компиляторов — даже когда is_max не уничтожается (подавал не константу компиляции) — эффект был тот же на -O2, всё те же oops для первоначального варианта. ))

CC>Алгоритмические оптимизации делаются во фронтэнде. И если он решил что тут будет всегда false, потому что на абстрактное (x + 1) < x он смотрел без учёта размерностей, то бэкенд именно return false, как ему сказали и сгенерирует.

Не, там же даётся дизассембинг кода. Происходит именно честный вызов is_max (если подавать не константу компиляции), вот его тело на экране, можно почитать ассемблерные инструкции и посмотреть, почему oops.

В режиме O1 и O2 в x64 у многих компиляторов не возникает переполнения, т.к. происходит инкремент 64-битного регистра (оптимизация заключается в замене сложения инкрементом, но нет команды инкремента полурегистра), т.е. результат инкремента всё еще продолжает оставаться положительным, поэтому задуманный первоначальный фокус и не срабатывает.

Именно поэтому вторым решением я предложил смотреть прямо в нужный бит, т.к. логику решений Синклер описал — максимальное неотрицательное число это такое, после прибавления единицы к которому вознрикает переполнение. В нужном бите возникает единичка, конечно, просто этот бит не всегда старший в промежуточных вычислениях. ))
Re[30]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 20.04.24 09:10
Оценка: :)
Здравствуйте, vdimas, Вы писали:


V>В режиме O1 и O2 в x64 у многих компиляторов не возникает переполнения, т.к. происходит инкремент 64-битного регистра (оптимизация заключается в замене сложения инкрементом, но нет команды инкремента полурегистра), т.е. результат инкремента всё еще продолжает оставаться положительным, поэтому задуманный первоначальный фокус и не срабатывает.

Зачем вы продолжаете писать бред?
1. Команда инкремента "полурегистра", конечно же, есть. Как и инкремент "четверти регистра". Как минимум на Интеле. Что не мешает компиляторам под интел (и при компиляции на интеле) выдавать неверный результат.
2. Понятие "положительности" результата тут нерелевантно. Главное — что и как сравнивать. Даже если мы выполнили инкремент через INC RAX (вместо INC EAX или INC AX), то ничего страшного.
Результат сравнения определяется вариантом инструкции CMP. Мы по-прежнему можем сравнить "полурегистры" или "четвертьрегистры", и весь мусор в старших битах будет проигнорирован.
А отличие знакового и беззнакового сравнения сводится к тому, какие флаги анализируются по его результатам.
3. Для is_max<long> ваши рассуждения неприменимы вообще. У нас в аргумент попадает 0x7FFFFFFFFFFFFFFF, после инкремента получается в 0x8000000000000000, что при знаковом сравнении меньше оригинала. Даже если бы компилятор писали полные идиоты, неспособные отличить int comparison от long comparison.

V>Именно поэтому вторым решением я предложил смотреть прямо в нужный бит, т.к. логику решений Синклер описал — максимальное неотрицательное число это такое, после прибавления единицы к которому вознрикает переполнение. В нужном бите возникает единичка, конечно, просто этот бит не всегда старший в промежуточных вычислениях. ))

Как вы объясните oops в тех случаях, когда этот бит — старший?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[30]: Carbon
От: CreatorCray  
Дата: 20.04.24 13:10
Оценка: +1
Здравствуйте, vdimas, Вы писали:

V>Не, там же даётся дизассембинг кода. Происходит именно честный вызов is_max (если подавать не константу компиляции)

Где? По всем ссылкам что тут были, где выводит "упс" я вижу всё заинлайненое, функция отсутствует.

V> вот его тело на экране, можно почитать ассемблерные инструкции и посмотреть, почему oops.

От спасибо, Кэп! Без тебя мы и не знали что это за корявки такие, ага!
Ты б хоть ссылку кинул про что ты говоришь, а то как то наблюдаемое в реальности на совпадает с твоими рассказами.

V>происходит инкремент 64-битного регистра (оптимизация заключается в замене сложения инкрементом, но нет команды инкремента полурегистра

Здрасте, нету! Весь набор присутствует: RAX -> EAX -> AX -> AL
Покрутил так и эдак — компилер для зтого значения упорно использует E а не R регистры, так что я хз на что ты там смотришь и теоретизируешь, показывай лучше код.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Отредактировано 21.04.2024 2:19 CreatorCray . Предыдущая версия .
Re[31]: Carbon
От: CreatorCray  
Дата: 20.04.24 13:10
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>2. Понятие "положительности" результата тут нерелевантно. Главное — что и как сравнивать. Даже если мы выполнили инкремент через INC RAX (вместо INC EAX или INC AX), то ничего страшного.

S>Результат сравнения определяется вариантом инструкции CMP
INC EAX выставит SF при знаковом переполнении, так что CMP там в общем то и не нужен.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re[32]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 21.04.24 06:07
Оценка: 1 (1)
Здравствуйте, CreatorCray, Вы писали:

CC>INC EAX выставит SF при знаковом переполнении, так что CMP там в общем то и не нужен.

Нужен. Если мы сделаем INC на -8 (0xFFFFFFF8), то получится 0xFFFFFFF9, и SF==1.
Прыжки вокруг флагов, инкрементов, и вычитаний нужны только оттого, что компилятор не может статически решить уравнение x+1<x.
То есть для signed типов он решение находит (просто неверное с т.з. человеческой логики), а для unsigned — увы.
Если бы мог, то он бы просто заменил всю арифметику на сравнение с единственным (для каждого типа) верным решением.
Поэтому если двигаться вдоль логики данного кода, то там будет инкремент, сравнение, конверсия флагов в результат.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: Carbon
От: diez_p  
Дата: 21.04.24 09:18
Оценка: +1
Здравствуйте, so5team, Вы писали:

S>Здравствуйте, rFLY, Вы писали:


FLY>>Это очевидно, но в чем профит для программиста от var,


S>Мне лично больше по душе писать что-то вроде:


S>
S>var variable = ...
S>const variable = ...
S>


S>
S>var variable = getSmth()
S> 
S>


var придумали для для тех, кто генерит код и потом его не читает. Какой тип под variable? если что, код еще часто читается с системы контроля версий, текстовых редакторов и т.д. уменьшение читабельности приводит к ошибкам и бОольшему времени на понимание.
Re[23]: Carbon
От: kov_serg Россия  
Дата: 21.04.24 10:54
Оценка: 2 (1)
Здравствуйте, Sinclair, Вы писали:

S>Давайте попробуем починить UB в функции, написанной просто и строго по делу, безо всяких шаблонов:

S>
S>long avg(long a, long b, long c)
S>{
S>  return (a+b+c)/3; 
S>}
S>

Переполнение можно убрать, заменив одни UB на другие
long avg(long a,long b,long c) {
    long r1=(a>>2)+(b>>2)+(c>>2), r2=r1%3; 
    r1-=r2; r2=4*r2+(a&3)+(b&3)+(c&3);
    if (r1<0 && r2>0) { r2-=12; r1++; }
    r1/=3; r2/=3; return (r1<<2)+r2;
}
Отредактировано 21.04.2024 10:55 kov_serg . Предыдущая версия .
Re[6]: Carbon
От: so5team https://stiffstream.com
Дата: 21.04.24 16:27
Оценка: :)
Здравствуйте, diez_p, Вы писали:

_>var придумали для для тех, кто генерит код и потом его не читает. Какой тип под variable?


Можете поупражняться в ручном выводе типов на примерах отсюда: https://rsdn.org/forum/flame.comp/8723434.1
Автор: so5team
Дата: 03.04 07:23

Если получится, то можно будет попробовать продолжить этот разговор.
Re[24]: Carbon
От: Sinclair Россия https://github.com/evilguest/
Дата: 21.04.24 18:30
Оценка: +1
Здравствуйте, kov_serg, Вы писали:

_>Здравствуйте, Sinclair, Вы писали:


S>>Давайте попробуем починить UB в функции, написанной просто и строго по делу, безо всяких шаблонов:

S>>
S>>long avg(long a, long b, long c)
S>>{
S>>  return (a+b+c)/3; 
S>>}
S>>

_>Переполнение можно убрать, заменив одни UB на другие
_>
_>long avg(long a,long b,long c) {
_>    long r1=(a>>2)+(b>>2)+(c>>2), r2=r1%3; 
_>    r1-=r2; r2=4*r2+(a&3)+(b&3)+(c&3);
_>    if (r1<0 && r2>0) { r2-=12; r1++; }
_>    r1/=3; r2/=3; return (r1<<2)+r2;
_>}
_>

Неплохо, неплохо. А зачем вы пользуетесь implementation-defined поведением? Вы же явно рассчитываете на то, что a >> 2 эквивалентно делению (на Интеле все рассмотренные на godbolt компиляторы делают это именно так; но, вообще говоря, компилятор не обязан делать арифметический сдвиг. А в случае логического сдвига будет ошибка.) Почему бы просто не поделить на 4?
Любой современный компилятор заменяет такие деления сдвигами (и на этот раз — ровно теми сдвигами, которые имеют нужную семантику, т.е. shr для unsigned, sar для signed).
До конца разобраться, что именно вы делаете, мне не удалось; но результат получается корректный
Я правильно понимаю, что вы хотели минимизировать количество делений?
Потому что очевидный вариант c семью делениями такой:
long avg(long a, long b, long c)
{
  long ar = a % 3, br = b % 3, cr = b % 3;
  return a / 3 + b / 3 + c / 3 + (ar+br+cr) / 3
}

Можно попытаться заставить компилятор сэкономить такты, скормив ему нарошные divrem функции:
long avg3(long a,long b,long c) {
    auto ad = std::ldiv(a, 3);
    auto bd = std::ldiv(b, 3);
    auto cd = std::ldiv(c, 3);
    return ad.quot + bd.quot + cd.quot + (ad.rem + bd.rem + cd.rem)/3;
}

Но на практике я смотрю в выхлоп кланга с -O3 — и, удивительное дело, для этого варианта он втыкает честные call ldiv@PLT, а для предыдушего — склеивает / и %, обходясь четырьмя imul-инструкциями. Подозреваю, что "экономия" тут получится отрицательная из-за накладных расходов на вызовы и связанных с ними перекладываний из регистра в регистр.
Надо бы
а) побенчмаркать это дело
б) посмотреть, нельзя ли как-то всё улучшить лучший из этих результатов, опираясь на defined behavior в случае переполнений.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 21.04.2024 19:19 Sinclair . Предыдущая версия . Еще …
Отредактировано 21.04.2024 19:18 Sinclair . Предыдущая версия .
Отредактировано 21.04.2024 19:17 Sinclair . Предыдущая версия .
Re[31]: Carbon
От: vdimas Россия  
Дата: 22.04.24 09:41
Оценка: :)
Здравствуйте, Sinclair, Вы писали:

S>1. Команда инкремента "полурегистра", конечно же, есть.


Увы, отдельно оперировать только младшим полурегистром невозможно.
Угадай содержимое регистра rax после такого кода:
    mov rax, 7FFFFFFF7FFFFFFFh
    inc eax


А после такого:
    mov rax, 7FFFFFFF7FFFFFFFh
    inc al



V>>Именно поэтому вторым решением я предложил смотреть прямо в нужный бит, т.к. логику решений Синклер описал — максимальное неотрицательное число это такое, после прибавления единицы к которому вознрикает переполнение. В нужном бите возникает единичка, конечно, просто этот бит не всегда старший в промежуточных вычислениях. ))

S>Как вы объясните oops в тех случаях, когда этот бит — старший?

"Сходи туда, не знаю куда". ))
Ты ж не дал ассемблерный листинг интересующего тебя кода, про который просишь пояснений.

===========
И вообще, слишком много умозрительных рассуждений безо всякого полезного выхлопа.

Добавь в Студии в плюсовый консольный проект файл f1.asm и установи ему в пропертях Custom build tool, затем сконфигурируй:

Command Line: ml64 /c /nologo /Zi /Fo "x64\Debug\f1.obj" /Fl"x64\Debug\f1.asm" /W3 /errorReport:prompt /Ta f1.asm
(будет собран obj с отладочной информацией)

Outputs: x64\Debug\f1.obj

Заготовка f1.asm
PUBLIC is_max

.code

is_max PROC
    ; скопируй сюда интересующий тебя код и ходи пошагово
    ret
is_max ENDP

END


Заготовка main.cpp:
#include <iostream>

extern "C" bool is_max(int);

int main()
{
    using namespace std;

    bool r = is_max(0x7FFFFFFF);
    cout << r << endl;

    return 0;
}


Развлекайся
Re[31]: Carbon
От: vdimas Россия  
Дата: 22.04.24 09:50
Оценка:
Здравствуйте, CreatorCray, Вы писали:

V>>Не, там же даётся дизассембинг кода. Происходит именно честный вызов is_max (если подавать не константу компиляции)

CC>Где? По всем ссылкам что тут были

Там можно выбрать компилятор, пройдись по сетке их и флаге O1, например.
(я брал последние номера компиляторов, а не trunk, бо в trunk у некоторых сидят старые версии, похоже давно они не обновляли сборки компиляторов из транков)


CC>где выводит "упс" я вижу всё заинлайненое, функция отсутствует.


Я находил, где присутствует тело, но тоже давало oops для некоторых типов (проверял весь набор char/short/int/longlong).

Причём, стоило добавить в тело is_max отладочную печать std::cout << (value+1) << std::endl;, т.е. никак не трогая целевые вычисления, не сохраняя промежуточный результат и т.д., но оно резко становилось wow ))

Квантовый эффект — наблюдение влияет на результат. ))
Отредактировано 22.04.2024 10:58 vdimas . Предыдущая версия . Еще …
Отредактировано 22.04.2024 10:57 vdimas . Предыдущая версия .
Re[32]: Carbon
От: CreatorCray  
Дата: 22.04.24 09:58
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Там можно компилятор, пройдись по сетке их и флаге O1, например.

Куда лучше если ты просто кинешь ссылку, чтоб мы все видели и обсуждали одно и то же.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re[29]: Carbon
От: vdimas Россия  
Дата: 22.04.24 10:03
Оценка: +1
Здравствуйте, Sinclair, Вы писали:

V>>Здравствуйте, Sinclair, Вы писали:

V>>Почему конкретнос в span<> использутся знаковые индексы — потому что это смещения, для которых как раз знаковые и пригодны, о чём я сразу же и сказал, а ты побежал спорить от "недостаточной компетенции" (С) Синклер.
S>Напомню, что в статье речь не только об индексах, но и о size(), который ну никак-никак отрицательным быть не может.

Это ты просто не видишь всю эту ползучую контрреволюцию целиком. ))
Подсказка — а Timespan может быть отрицательным? А Datetime? Но они прекрасно складываются и вычитаются.
(впрочем, об этом было сказано сразу же, таких примеров полно, где совместная арифметика знаковых и беззнаковых логична и обоснована)

В общем, всю эту хрень уже обсуждали как-то по всему интернету несколько лет назад, и однозначного согласия как не было, так и не будет никогда.
Стандарт не принимается одним человеком, и там приняли вот так:
https://en.cppreference.com/w/cpp/container/span
https://en.cppreference.com/w/cpp/container/span/subspan

Потому что Страуструп предлагает лечение костылей коньками.
Т.е. предлагает решение другой задачи вместо целевой.

А если решать целевую задачу...
Учитывая легковесность оберток в С++, не факт, что решение целевой задачи должно означать введение специальных встроенных типов для индексов в язык, дабы иметь над ними соотв. математику знаковых/беззнаковых.

Вдогонку.
Тем более, что в язык ввели user-defined литералы:
struct Index {
    unsigned long long i_;
    explicit constexpr Index(const auto i) : i_(i) {}
};

Index operator ""_i(unsigned long long i) {
    return Index(i);
}

auto i = 1_i;
Отредактировано 22.04.2024 10:50 vdimas . Предыдущая версия .
Re[29]: Carbon
От: vdimas Россия  
Дата: 22.04.24 10:21
Оценка: -1
Здравствуйте, Sinclair, Вы писали:

S>Следите за руками: в дотнете прибавление единицы к int 0x7FFFFFFF даёт int 0x80000000 (overflows are ignored). Здесь нет никаких бит для отбрасывания, т.к. весь результат fits in the destination type.

S>А в C++ такое прибавление даёт undefined behavior.

Продолжаешь пытаться показывать фокусы? ))
Если в этот же код подать другое число, то может быть отбрасывание.

========
Это даже уже не фокус у тебя, это мошенничество в расчёте на однобитную тусовку, один завсегдатай уже отметился.

Тебе же уже говорилось, что промежуточные вычисления в более широких типах допустимы прямо по стандарту.

И раздражает твоё непонимание понятия UB.
UB не означает ошибку, UB означает неопределённое поведение в случае ошибки.

Т.е. вот у тебя в формуле встречается участок (a+b)/2, в котором может наступить переполнение и выдача ошибочного результата.
Разумеется, до факта переполнения формула остаётся работоспособной.
А после переполнения — неопределено, т.е. может остаться работоспособной, а может и не остаться.
На этот эффект ты и нарвался в своём примере.

И что ты рядом бил себя в грудь, что в дотнете формула достоверно будет неработоспособной — это потому что дотнет пока мест всё еще детская игрушка.
Займутся им всёрьез — будет так же.

И значешь почему?
Потому что принципиально современные разрабы-оптимизаторы кучкуются только в С++, и других взять тупо неоткуда. ))

Собсно, уже в дотнет нагнали плюсовиков и те стали приводить сие полумёртвое поделие в норму.
Отредактировано 22.04.2024 12:01 vdimas . Предыдущая версия . Еще …
Отредактировано 22.04.2024 10:26 vdimas . Предыдущая версия .
Re[33]: Carbon
От: vdimas Россия  
Дата: 22.04.24 20:02
Оценка:
Здравствуйте, Sinclair, Вы писали:

CC>>INC EAX выставит SF при знаковом переполнении, так что CMP там в общем то и не нужен.

S>Нужен. Если мы сделаем INC на -8 (0xFFFFFFF8), то получится 0xFFFFFFF9, и SF==1.
S>Прыжки вокруг флагов, инкрементов, и вычитаний нужны только оттого, что компилятор не может статически решить уравнение x+1<x.

Тут стоит задасться вопросом — а зачем компилятор вообще пытается решить такие "уравнения"?

Это из-за шаблонов, из-за сильного раздутия бинарников по мере того, как шаблонный код становился всё более популярным когда-то.
Такой подход позволяет обрезать из шаблонного кода ненужные ветки где только возможно дотянуться анализом кода.
Но оно же работает для любого инлайна.

В дотнете я как-то показывал трюк эмуляции числовых-константных параметров шаблонов:
https://godbolt.org/z/fKvE14G33
using System;
using System.Runtime.CompilerServices;

interface IYesNo { 
    bool Value { get; }
}

struct Yes : IYesNo {
    public bool Value => true;
}

struct No : IYesNo {
    public bool Value => false;
}

class SomeType<TConfig> where TConfig : struct, IYesNo
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Method() {
        if(default(TConfig).Value) {
            Console.WriteLine("Yes");
        }
        else {
            Console.WriteLine("No");
        }
    }
}

class Program
{
    static void Main()  { 
        SomeType<Yes>.Method();
        SomeType<No>.Method();
    }
}

В примитивных случаях работает — уходит бранчинг, половина кода метода выбрасывается за ненадобностью.

Кстате, в 8-м дотнете в сравнении с 5-6-ми, где я в последний раз проверял этот трюк, наконец-то убрали ненужную двойную инициализацию временного значения default(TConfig), где сгенерённый код выглядел полнейшим нубством.

Вот для 8-го дотнета:
Program:Main() (FullOpts):
       push     rbp
       mov      rbp, rsp
       mov      rdi, 0x7F2C74BD06E8      ; 'Yes'
       call     [System.Console:WriteLine(System.String)]
       mov      rdi, 0x7F2C74BD0708      ; 'No'
       call     [System.Console:WriteLine(System.String)]
       nop      
       pop      rbp
       ret


Вот для 6-го:
Program:Main():
       push     rbp
       sub      rsp, 16
       lea      rbp, [rsp+10H]
       mov      byte  ptr [rbp-08H], 0
       lea      rdi, bword ptr [rbp-08H]
       mov      byte  ptr [rdi], 0
       mov      rdi, qword ptr [(reloc)]
       mov      rdi, gword ptr [rdi]
       call     [System.Console:WriteLine(System.String)]
       mov      byte  ptr [rbp-10H], 0
       lea      rdi, bword ptr [rbp-10H]
       mov      byte  ptr [rdi], 0
       mov      rdi, qword ptr [(reloc)]
       mov      rdi, gword ptr [rdi]
       call     [System.Console:WriteLine(System.String)]
       nop      
       add      rsp, 16
       pop      rbp
       ret


В общем, за оптимизацию дотнета явно взялись, и я это наблюдаю не только по этому трюку, просто не делаю полный список замеченного.


S>То есть для signed типов он решение находит (просто неверное с т.з. человеческой логики), а для unsigned — увы.


Для сравнения, дотнет ничего не ищет, тупо исполняет:
Program:IsMax(int):bool:
       lea      eax, [rdi+01H]
       cmp      eax, edi
       setl     al
       movzx    rax, al
       ret

Забавно, что сложение выполняется в 64 бит, да еще через трюк адресной арифметики, но запоминаются младшие 32 бит результата.


S>Если бы мог, то он бы просто заменил всю арифметику на сравнение с единственным (для каждого типа) верным решением.


Для этого требуется сначала объявить переполнение знаковых не потенциальной ошибкой, а нормой.
Отрасль не поймёт. ))

К тому же, C# разрабатывается одной конторой, а для плюсов когда-то было большой победой, что куча независимых компиляторов стали понимать исходный код друг друга.

Плюс, некоторые UB довольно-таки, забавны.
Например, вызов метода объекта, когда не обращаются данным объекта, т.е. ни к виртуальному методу, ни к полю.
Например, в C#:
https://godbolt.org/z/rnaxjo4Kv
using System;

class SomeClass {
    public void Method() {
        Console.WriteLine("!!!");
    }
}

class Program
{
    static void Main()
    {
        SomeClass? sc = null;
        var s = Console.ReadLine();
        if(s.Length > 1)
          sc = new SomeClass();

        sc.Method();
    }
}

Вызов sc.Method() полностью заинлайнился, обращения к this нет, но в коде перед вызовом метода всё-равно стоит проверка, что адрес sc валидный:
cmp      byte  ptr [rbx], bl

(если в rbx будет null, то сгенерируется прерывание AV)
А sc!.Method() убирает лишь ворнинг.

В плюсах это UB, но, например, в MSVC прекрасно работает:
#include <iostream>

using namespace std;

struct SomeObj {
    void Method() {
        std::cout << "!!!" << std::endl;
    }
};

int main()
{
    int i;
    cin >> i;

    SomeObj * obj = nullptr;

    if(i > 0)
        obj = new SomeObj();

    obj->Method();

    return 0;
}

Скомпиллировано как написано, условно создаётся объект obj, вызов метода заинлайнился, обращения к this нет, хотя вызов метода через nullptr.

Clang безусловно создаёт ненужный объект, потом вызывает заинлайненное тело.
GCC безусловно вызывает заинлайненное тело, никогда не выделяя память в куче для ветки obj = new SomeObj().

И кто тут прав?

C#, который делает лишнюю проверку в рантайм перед вызовом практически каждого метода (nullable-аннотация не помогает), хотя зачастую нет обращения к this?
Или MSVC, который исполняет ненужный бранчинг?
Или Clang, который создаёт ненужный объект?
Или GCC, который не создаёт ненужный объект, но даже косвенно про проблему узнать не получится, бо нет утечки памяти? ))
Отредактировано 22.04.2024 20:07 vdimas . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.