Целочисленное преобразование с нулевой стоимостью посредством встроенной сборки

Можно ли создать в виде макроса своего рода целочисленное преобразование с нулевой стоимостью с помощью встроенной сборки?

Во многих случаях мне хотелось бы передавать 32-битные целые числа, которые уже «на месте», через интерфейсы, принимающие 64-битные целые числа. Но C не любит оставлять старшие биты неопределенными, а приведение или попытка расширить 32 бита до 64 с помощью объединения заставляет компиляторы либо расширять ноль, либо расширять знак 32-битного числа.

Фиктивный пример:

void g(unsigned long );
void f(int X){
    unsigned long XX = (unsigned)X; //mov %edi, %edi
    g(XX);
}

Можно ли заменить гипс на какой-нибудь JUST_PRETEND_ITS_AN_UNSIGNED_LONG_WITHOUT_DOING_ANYTHING(X)?

Какой ты хочешь? расширение нуля или расширение знака? Где размер лонга больше, решать вам.

Simon Goater 29.05.2024 21:26

@SimonGoater Ни то, ни другое. Оставьте верхние биты такими, какими они были.

Petr Skocik 29.05.2024 21:27

Есть проблемы с переносимостью, но вы можете сделать это с помощью объединения элементов int x[2] и unsigned long.

Simon Goater 29.05.2024 21:30

@SimonGoater Как я уже сказал в вопросе, союзы, которые я уже пробовал: godbolt.org/z/WTrdx34Tf => нулевое расширение.

Petr Skocik 29.05.2024 21:31

По моему предложению вам нужно создать массив int в объединении. Вы это пробовали?

Simon Goater 29.05.2024 21:32

То есть функция принимает беззнаковый длинный номер, но игнорирует старшие 32 бита, поэтому их можно оставить неопределенными?

Barmar 29.05.2024 21:33

@Бармар Правильно. Я использую longs (uintptr_t) просто как хранилище контекста для некоторых обратных вызовов. Некоторые обратные вызовы используют полные 64 бита. Другие понизят его до 32-битного, поэтому для них вполне нормально, что старшие биты будут мусором.

Petr Skocik 29.05.2024 21:36

Сделать f (как показано) с помощью __attribute__((always_inline))? AFAICT, это настолько быстро, насколько это возможно. При использовании inline вызывающая сторона f может генерировать 32-битные значения напрямую. В любом случае, разве речь не идет об одном ассемблерном инсте?

Craig Estey 29.05.2024 22:09

Что заставляет вас думать, что здесь можно сэкономить? Или, если бы они были, этого было бы достаточно, чтобы оправдать любые усилия в этом направлении?

John Bollinger 29.05.2024 22:12

Обратите внимание, что все аргументы передаются по значению, поэтому в любом случае будет выполнено копирование. На большинстве платформ это будет в регистре, и в представленном случае я ожидаю, что достойный компилятор уже будет использовать эффективный (вероятно, самый эффективный) механизм загрузки 64-битного регистра с 32-битным значением. , с расширением знака или без него, как того требует комбинация типов данных C. Я не вижу причин ожидать, что вы сможете добавить код (даже сборку), чтобы сделать его более эффективным.

John Bollinger 29.05.2024 22:16

@JohnBollinger Отличный вопрос. Я опубликовал супер-пустышный пример. На самом деле у меня есть много того и этого, каждый из которых принимает шесть аргументов контекста uintptr_t, и это организовано таким образом, чтобы сохранить размер кода, и компилятор немного испортил это с этими нулевыми расширениями, которые складываются. Возможно, я не буду использовать то, что нашел, но было интересно исследовать это. :D Опубликовал свою находку благодаря тем, кто проголосовал за этот вопрос. Я был готов удалить его из-за отрицательного голоса. :)

Petr Skocik 29.05.2024 22:19

@JohnBollinger В коде, с которым я работаю, все уже организовано для того, чтобы аргументы были на месте (я беру указатели на функции в качестве последних аргументов), поэтому, если нет знака/расширяющего ноль mov, не будет никаких mov => экономия кода <=> то, что мне было нужно.

Petr Skocik 29.05.2024 22:21

При использовании inline вызывающий f должен вычислить значение X. Скажем, это значение находится в eax/rax. Надо это поставить edi/rdi. Это делается с помощью mov* inst. Он выяснит, является ли mov простым, расширяющимся со знаком или расширяющимся с нуля. Это нулевые накладные расходы. Абонент [вероятно] должен сгенерировать это mov в любом случае

Craig Estey 29.05.2024 22:29

@PetrSkocik, мне трудно представить ситуацию, которую ты описываешь. Если пример, представленный в вопросе, не соответствует вашему реальному случаю, это, вероятно, означает, что вы задали неправильный вопрос. Я все еще не думаю, что вы добьетесь большего, не отказавшись от C полностью (и, возможно, даже тогда), но я вполне уверен, что вы не получите ответ, который ищете, с помощью вопроса в его нынешней формулировке.

John Bollinger 29.05.2024 22:48

Просто объявите f, чтобы получить unsigned long. Тогда компилятору не придется «притворяться», что это беззнаковый длинный тип. это беззнаковый длинный.

Raymond Chen 29.05.2024 23:08

В качестве альтернативы вашему профсоюзному коду, как насчет asm volatile ("jmp g\n");, поскольку именно его вы пытаетесь создать. В конце может быть дополнительный (не выполненный) ret, но кого это волнует?

Craig Estey 29.05.2024 23:43

@CraigEstey Есть много способов добиться этого кода. Я искал только то, о чем задавался вопрос, потому что это сочетается с другими вещами. Ответ, который я опубликовал, достаточно отвечает на этот вопрос.

Petr Skocik 29.05.2024 23:50

Опять же, речь идет об исключении одного mov инста. Что бы g ни делал, сейчас он находится под огромным давлением, чтобы не потратить сэкономленные средства напрасно, введя хоть один дополнительный момент ;-)

Craig Estey 29.05.2024 23:52

Я бы [снова] спросил, может ли f быть встроенным?

Craig Estey 29.05.2024 23:54

@CraigEstey фиктивные примеры! = реальный код. И нет, встраивание неприменимо/не полезно в моем случае использования.

Petr Skocik 29.05.2024 23:55

Тогда у вас здесь нет MRE для вашего реального кода. Нам потребуются примеры всех соответствующих g функций с соответствующими f вариантами. На мой взгляд, вам нужны разные f версии, которые вызывают желаемое поведение: без изменений, расширение знака, расширение нуля и т. д. Затем мы могли бы предложить способы их дальнейшей оптимизации/улучшения, чего вы и хотели, я полагаю.

Craig Estey 30.05.2024 00:08

@JohnBollinger язык указывает, что в исходном коде старшим 32 битам параметра должны быть присвоены значения. Компилятор не может это оптимизировать. (Ну, теоретически он мог бы проанализировать вызываемую функцию, чтобы увидеть, используются ли биты, но на самом деле компиляторы этого делать не собираются).

M.M 30.05.2024 01:35

Конечно, @М.М. Когда я сказал «с расширением знака или без него, как того требует комбинация типов данных C», я имел в виду расширение знака, а не нулевое расширение, а не «сохранять случайные биты». Как это обычно бывает в C, семантика определяется в терминах значений, а не их представлений.

John Bollinger 30.05.2024 02:21

Вы можете использовать опцию "<digit>" встроенного ассемблера gcc, чтобы указать, что входные и выходные данные должны быть одним и тем же регистром, а затем ничего не делать: void f(int X) { unsigned long XX; __asm__("" : "=r"(XX) : "0" (X)); g(XX); } Это автоматически адаптируется к случаю, когда параметр передается в стек или если выравнивание или Ограничение упаковки предотвращает совмещение этих двух значений.

Raymond Chen 30.05.2024 15:42

@RaymondChen Спасибо. Да. Ограничение <digit> — это то, что я искал. (Я понял это вчера и опубликовал это как ответ на случай, если другие захотят этого)

Petr Skocik 30.05.2024 18:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
25
180
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Комбинация объединений и ограничений соответствия встроенных сборок, похоже, сделала это:

#include <stdint.h>
void takeUptr(uintptr_t U);
#define upcast(X)  ({ union { int x; uintptr_t xx; } u; u.x = X; \
    asm("":"=r"(u.xx):"0"(u.x)); /*make C think the upper bits are inited*/ \
    u.xx; })

void passAsUptr(int x){ takeUptr(upcast(x)); } 
//^jmp takeUptr; //no zero/sign-extending mov ✓
uintptr_t retAsUptr(int x){ return upcast(x); } 
//^movl %edi, %eax; ret; //correctly moved to the output register ✓

https://godbolt.org/z/vz9W5Mq8M

В gcc (к сожалению, не в clang) это устраняет mov, расширяющий ноль при переходе от от 32-битного до 64-битного числа, если они оба сопоставлены с одним и тем же регистром. Ключевым моментом было использование позиционного ограничения 0, чтобы регистр ввода (здесь u.x) соответствовал регистру вывода (u.xx, в позиции 0, следовательно, ограничение 0).

Код неправильный. Проблема в том, что на самом деле нет гарантии, что верхние биты %rdi чисты.

Joshua 29.05.2024 22:38

@Джошуа И? Я не хочу, чтобы они были ясными. Я хочу, чтобы они были такими, какими они были, не заставляя компилятор вставлять mov, расширяющий ноль/знак. В этом весь смысл.

Petr Skocik 29.05.2024 22:40

Мгновенно -2, не принимать никаких обязательств при проверке кода.

n. m. could be an AI 30.05.2024 00:10

@n.m.couldbeanAI :D Ну, его всегда можно предварительно подготовить, инкапсулировать и использовать обычное приведение на платформах, отличных от x86-64. Но мне приятно знать, что есть простой способ сделать эту очень специфическую микроштуку :).

Petr Skocik 30.05.2024 00:18

Когда u.xx шире, чем u.x, неясно, какие 32-битные из 64-битных u.xx установлены: верхняя или нижняя половина. Это делает код менее переносимым и требует большего обслуживания.

chux - Reinstate Monica 30.05.2024 00:27

Я думаю, н.м. найдите настоящую причину [не делать этого]. Для объяснения потребовался бы абзац комментариев/документации (со всеми обоснованиями против представленных здесь контраргументов). А как насчет тех версий g, которые маскируют/игнорируют старшие 32 бита? Предположим, через год или около того другой программист работает над g и понимает, что может удалить маскировку в g, потому что ожидает чистый, хорошо сформированный unsigned long. Они никогда не увидят вашу оптимизированную f функцию, которая дает им «грязный» unsigned long. Скрытая ошибка? Или во всех местах требуется много комментариев?

Craig Estey 30.05.2024 00:38

@CraigEstey Вот более полный контекст: у меня есть функция типа long applyCallbackInContext(long,long,long,long,long, long (*callback)(long,long,long,long,long)) Tail, вызываемая из многих функций, у которых уже есть первые 5 аргументов, но во многих случаях некоторые или все из них являются не полными длинными значениями, а скорее целыми. Если бы не нулевые расширяющие перемещения, все это можно было бы сделать просто загрузкой указателя функции с последующим jmp на x86-64 (основная цель). Обратные вызовы будут возвращаться к исходным типам, поэтому любое расширение знака/ноля является просто пустой тратой, и оно накапливается из-за наличия большого количества таких оболочек.

Petr Skocik 30.05.2024 11:23

@CraigEstey Оставлять вещи неопределенными, когда вы не собираетесь их использовать, не так уж редко и трудно понять в C. Вышеупомянутое позволяет вам делать это и для частей регистра. Используйте его или нет. Выше описано, как это можно сделать.

Petr Skocik 30.05.2024 11:25
inline long long upcast(int v) { long long l; __asm__("" : "=r"(l) : "0" (v)); return l; } избегает макроса.
Raymond Chen 30.05.2024 21:12

@RaymondChen Очень хорошее улучшение. Мне нравится, как это покончило с профсоюзом. Не знаю, почему я решил, что мне это нужно. Спасибо!

Petr Skocik 30.05.2024 21:22

Другие вопросы по теме