gcc и деление
От: maks1180  
Дата: 02.01.22 00:37
Оценка: -1
на x32 платформе компилирую через gcc v10 следующий код:
volatile uint64_t v = 11;
volatile uint32_t v2 = v / 5;

и вижу в ассемблере:
mov eax,DWORD PTR [esp+0x18]
mov edx,DWORD PTR [esp+0x1c]
mov DWORD PTR [esp+0x8],0x5
mov DWORD PTR [esp+0xc],0x0
mov DWORD PTR [esp],eax
mov DWORD PTR [esp+0x4],edx
call 4024d0 <___udivdi3>
mov DWORD PTR [esp+0x14],eax

___udivdi3 — очень не быстрая функция которая делает div сдвиги и много чего ещё.
Почему просто не использовать div, ведь "div ecx" делит EDX:EAX (64 битное число) на 32-х битное ECX ?
===============================================
(реклама, удалена модератором)
Re: gcc и деление
От: watchmaker  
Дата: 02.01.22 01:33
Оценка: 3 (1) +1
Здравствуйте, maks1180, Вы писали:


M>___udivdi3 — очень не быстрая функция которая делает div сдвиги и много чего ещё.

M>Почему просто не использовать div, ведь "div ecx" делит EDX:EAX (64 битное число) на 32-х битное ECX ?

Просто использовать не получится. Важное свойство этой инструкции:

Overflow is indicated with the #DE (divide error) exception

т.е как минимум нормальная работа программы при переполнении закончится и (если нет особого обработчика) будет аварийное завершение.

И это задача того, кто написал инструкцию div, следить, чтобы её аргументы не вызывали переполнения.
Вот функция ___udivdi3 этим примерно и занимается — вызывает деление безопасным образом, чтобы результат получился именно тем, что написан в С++ коде.


M>на x32 платформе компилирую через gcc v10


Вообще, при делении на константу вызывать ___udivdi3 или использовать инструкцию div — неоптимальный вариант, если оптимизируется именно скорость работы (а не размер кода, например).
Все делают умножение на обратное значение, которое для констант считается во время компиляции.
В gcc 11 поправили: https://gcc.godbolt.org/z/Ga8vcPGK4
Отредактировано 02.01.2022 4:39 watchmaker . Предыдущая версия .
Re[2]: gcc и деление
От: reversecode google
Дата: 02.01.22 01:48
Оценка:
все еще проще
результат div на 32 битной платформе это 32 число
хотя 64 бита/32 бита = может быть 64 бита

а поскольку у него значение volatile, то компилер вывести делимое не может
и генерирует функцию

делаем const и все как и ожидаем
    const uint64_t v = 4;
    volatile uint32_t d = 2;
    volatile uint32_t v2 = v / d;

все ок
Re[3]: gcc и деление
От: maks1180  
Дата: 02.01.22 02:01
Оценка:
R>все еще проще
R>результат div на 32 битной платформе это 32 число
R>хотя 64 бита/32 бита = может быть 64 бита

Да, может быть, но мы результат всё равно запихиваем в uint32
===============================================
(реклама, удалена модератором)
Re[2]: gcc и деление
От: maks1180  
Дата: 02.01.22 02:02
Оценка:
W>Просто использовать не получится. Важное свойство этой инструкции:
W>

Overflow is indicated with the #DE (divide error) exception

т.е как минимум нормальная работа программы при переполнении закончится и (если нет особого обработчика) будет аварийное завершение.


Это же правильно, так и должна работать программа.
А как по вашему должен поступить с++ в этом случаи ?
===============================================
(реклама, удалена модератором)
Re[2]: gcc и деление
От: maks1180  
Дата: 02.01.22 02:18
Оценка:
W>В gcc 11 поправили: https://gcc.godbolt.org/z/Ga8vcPGK4

Классная ссылка, спасибо!
Только почему она не показывает код My::Test() ?

volatile double t;

class My {
public:
virtual void Test(int a) {
t = a;
}
};

void Test(double v, My* my) {
my->Test(3);
}
===============================================
(реклама, удалена модератором)
Отредактировано 02.01.2022 2:29 maks1180 . Предыдущая версия .
Re[3]: gcc и деление
От: watchmaker  
Дата: 02.01.22 02:48
Оценка:
Здравствуйте, maks1180, Вы писали:

W>>

Overflow is indicated with the #DE (divide error) exception

т.е как минимум нормальная работа программы при переполнении закончится и (если нет особого обработчика) будет аварийное завершение.


M>Это же правильно, так и должна работать программа.

M>А как по вашему должен поступить с++ в этом случаи ?

Вывернуто всё наоборот. Зачем С++ должен как-то поступать?

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

Если временно забыть о volatile, то поведение кода
uint64_t v = ...
uint32_t v2 = v / 5;

описано в
https://eel.is/c++draft/expr.mul#2
https://eel.is/c++draft/expr.arith.conv#1.5.3
https://eel.is/c++draft/expr.mul#4
https://eel.is/c++draft/conv#integral-3

volatile дополнительно запрещает делать предположения о содержимом памяти, но это ничего не меняет в вопросе о поведении операции деления.

И компилятор должен сделать так, чтобы результат при вычислении на железе совпал с тем, который описан в стандарте языка.
Разрешено в языке выполнять деление 81985526925837671 / 5? Разрешено! Является результат однозначным и определённым? Является. Может такая пара встретится в коде ([1]
Автор: maks1180
Дата: 02.01.22
или [2])? Может!
Вот и компилятор должен сгенерировать код, который позволит получить правильный результат.
И не важно, что на x86 инструкция div не умеет делить такую пару чисел.
Re[4]: gcc и деление
От: maks1180  
Дата: 02.01.22 12:51
Оценка:
Т.е. с сделает код аналогичный uint32_t v2 = (v / 5) % (2^32); ?
===============================================
(реклама, удалена модератором)
Re[3]: gcc и деление
От: maks1180  
Дата: 03.01.22 16:32
Оценка:
Разобрался! Нужно было добавить глобальный объект "My m;". Странно что без него gcc решил, что код My можно выкинуть, хотя сслыка на него была использована.
===============================================
(реклама, удалена модератором)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.