Функция XCode и _bittest

У меня есть небольшой проект на C++, разработанный для Win32, и я хочу перенести его на OSX. В коде используются такие функции, как _bittest и _bittest64, но я не нашел таких функций в файлах заголовков XCode.

Что может быть альтернативой этим функциям? Может быть есть хорошие рабочие полифиллы. Проект действительно унаследован, на данный момент не требуется дополнительной производительности.

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

Some programmer dude 13.06.2018 10:29

В основном дубликат Использование инструкции по сборке bts с компилятором gcc. Как объясняется в моем ответе, вам не нужна фактическая инструкция на современном x86. (Хотя компиляторы упустили оптимизацию для использования версии bt и bts с регистром-регистром, которые работают быстро как на Intel, так и на AMD.)

Peter Cordes 13.06.2018 11:39
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
483
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Символы _bittest и _bittest64 являются внутренними функциями компилятора, которые выдают Инструкции по битовому тестированию, в частности x86 bt, для проверки значения бита в индексе, начинающемся с нуля.

С операндом памяти bt имеет сумасшедшее поведение цепочки битов CISC, когда битовый индекс может выходить за пределы двойного / q-слова памяти, выбранного режимом адресации. Это медленно, и поэтому компиляторы сначала загружают операнд в регистр. Но это то, для чего предназначен встроенный MSVC. В противном случае это не обязательно должно быть внутренним.

Следующий C++ соответствует поведению версии с регистром-аргументом инструкции bt, оборачивая счетчик сдвига по ширине регистра, то есть эффективно просматривая только младшие биты. (Это соответствует внутреннему MSVC если b <32 или <64.) См. Обновленный код и комментарии для обсуждения того, как реализовать семантику MSVC, которая позволяет ему осуществлять доступ за пределами указанного long или long long.

Также помните, что long является 32-разрядным типом в x64 Windows ABI, но 64-разрядным типом в x86-64 System V ABI (который вы используете в OS X, если вы не создаете устаревший 32-разрядный код). Вы можете изменить свой код на int32_t или uint32_t, чтобы не оставлять неиспользуемые биты в каждом long, в зависимости от того, как вы его используете.

inline
unsigned char bittest(long const *a, long b)
{
    auto const value{ *a };
    auto const mask{ 1L << (b&31) };
    auto const masked_value{ value & mask };
    return unsigned char{ masked_value != 0 };
}

inline
unsigned char bittest64(long long const *a, long long b)
{
    auto const value{ *a };
    auto const mask{ 1LL << (b&63) };
    auto const masked_value{ value & mask };
    return unsigned char{ masked_value != 0 };
}

Мне неизвестны встроенные функции GCC или Clang с идентичной функциональностью. При необходимости вы можете вместо этого прибегнуть к передаче инструкций сборки из реализаций функций, но bt с операндом памяти работает медленно, поэтому обычно лучше реализовать на чистом C++ и позволить компилятору хорошо выполнять свою работу.

Обновлять:

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

inline
unsigned char bittest(std::int32_t const *a, std::int32_t b)
{
    auto const bits{ reinterpret_cast<unsigned char const*>(a) };
    auto const value{ bits[b >> 3] };
    auto const mask{ (unsigned char)(1 << (b & 7)) };
    return (value & mask) != 0;
}

inline
unsigned char bittest64(std::int64_t const *a, std::int64_t b)
{
    auto const bits{ reinterpret_cast<unsigned char const*>(a) };
    auto const value{ bits[b >> 3] };
    auto const mask{ (unsigned char)(1 << (b & 7)) };
    return (value & mask) != 0;
}

Зачем вообще здесь нужен указатель arg? Это не асм; это зависит от компилятора, где значение находится после встраивания. Так что проще просто взять аргумент по значению. Вы все равно хотите, чтобы эти функции были встроенными, поэтому вы не получите инструкций по фактическому созданию логического возвращаемого значения в целочисленном регистре.

Peter Cordes 13.06.2018 11:39

@PeterCordes: Эти функции предназначены для замены 1: 1, поэтому я оставил интерфейс без изменений. OP переносит код и, по-видимому, не хочет изменять вызывающий код.

IInspectable 13.06.2018 11:42

Хм, я добавил &31 и &63 в счетчик сдвигов, потому что x86 действительно маскирует счетчик сдвигов. Но в портативной версии этого, вероятно, следует использовать sizeof(long)*CHAR_BIT - 1 или эквивалент C++.

Peter Cordes 13.06.2018 11:42

Да ладно, я забыл о том, что конечным источником вопроса является внутренняя функция MSVC: P. Хм, я дважды проверю, что внутренняя функция на самом деле не для версии, предназначенной для памяти, где нам действительно нужно обрабатывать индексацию вне *a.

Peter Cordes 13.06.2018 11:43

@PeterCordes: Переносимая версия, вероятно, должна оставить код как есть, а static_assert с фактическими целочисленными размерами. В конце концов, исходная реализация ожидает 32 и 64 значения и не подстраивается под фактические размеры, используемые языковой реализацией.

IInspectable 13.06.2018 11:45

Или, возможно, использовать int64_t / int32_t? Нет, вероятно, лучше всего использовать long и long long для совместимости с псевдонимами.

Peter Cordes 13.06.2018 11:47

@PeterCordes: К сожалению, целочисленные типы фиксированной ширины не являются обязательными в C++. Соответствующая реализация может отказаться от поставки любого из них.

IInspectable 13.06.2018 11:49

Да, но более или менее безопасно предположить, что любая реализация, в которой long или int является типом со знаком с дополнением до 2 и правильной шириной, будет иметь int32_t. Это является, что мы хотим проверить (за исключением того, что, я думаю, нам не нужно требовать дополнения до 2. Вероятно, нам следует привести к uint32_t, потому что MSVC long - это 32-разрядный тип, но это 64-разрядный тип в x86-64 MacOS Так что битовая индексация все равно будет нарушена.

Peter Cordes 13.06.2018 11:56

Я проверил, и MSVC _bittest действительно использует битовый индекс напрямую с операндом памяти. godbolt.org/g/Ev42LS. Думаю, в этом весь смысл и почему это должна быть внутренняя оптимизация, а не оптимизация глазком: это индекс может вне *a, в частности, для a[b>>3]. Вероятно, мы хотим unsigned char и не гарантируем отсутствие отступов.

Peter Cordes 13.06.2018 11:59

@PeterCordes: Другими словами: функции замены должны быть расширены, чтобы позволить произвольную индексацию. Я не уверен, что понимаю, где здесь задействован unsigned char. Вы предлагаете поменять интерфейс? Если нет, то я не уверен, что мне даст кастинг на unsigned char.

IInspectable 13.06.2018 12:19
unsigned char безопасен со строгим алиасингом (например, я предполагаю, что _bittest находится на MSVC, если MSVC вообще оптимизирует на основе строгого алиасинга). И это дает нам портативный битовый доступ к объектному представлению long. Обычно у нас этого нет, потому что long может иметь заполнение, а unsigned char - нет. Мы можем взять const long * и преобразовать его в const char * внутри функции.
Peter Cordes 13.06.2018 12:21

@PeterCordes: Будет ли эта реализация демонстрировать поведение, идентичное внутреннему поведению _bittest?

IInspectable 13.06.2018 12:56

Да, я только что проверил настоящую инструкцию, и индекс массива со знаком верен. (например, mov edx, -20*8 / bt [buf+100], edx действительно обращается к [buf+80] в 64-битном режиме, где указатели являются 64-битными, что доказывает, что знак-расширение счетчика до 64-битного является правильным, как и ваш код. И, кстати, много легче читать при оптимизации Вы исключили -Ox / -O3. godbolt.org/g/PBUbV9) О, я не проверял, что b >>3 правильный, а не b / 8. Для отрицательного значения b они округляются по-разному, и в справочном руководстве ISA указано «count DIV 32» для двойного слова, к которому обращается 32-битная версия операнда.

Peter Cordes 13.06.2018 13:35
>> 3 правильный. Нам нужен неотрицательный остаток для индексации в выбранный char. Я дважды проверил фактическое поведение с mov edx, -10 / bt [buf+8], edx, и он установил [buf+6] на 0x40 (с 0). bts использует ту же индексацию, что и bt.
Peter Cordes 13.06.2018 14:03

И, кстати, это должен быть арифметический сдвиг вправо. Не уверен, что int32_t гарантирует что, или он все еще определен в реализации.

Peter Cordes 13.06.2018 14:16

Хорошее обновление. К сожалению, использование char* и разделение битового индекса на байтовые / битовые компоненты, вероятно, не оптимизирует для случая b=13 или чего-то еще. Надеюсь, эти встроенные функции будут использоваться только с массивами, которые уже находятся в памяти, а не с одной скалярной переменной, и в этом случае это может не иметь значения (много).

Peter Cordes 14.06.2018 10:03

@PeterCordes: Я обновил ответ, включив в него код, реализующий полный набор функций встроенных функций. Я не нашел документации, которая так или иначе обращалась бы к int32_t, гарантируя арифметический сдвиг вправо. Это может показаться естественным, учитывая, что он гарантированно использует дополнение до 2, и все компиляторы также выдают инструкцию sar.

IInspectable 14.06.2018 10:03

Да, я не знаю каких-либо существующих реализаций C++ на машинах с двумя дополнениями, которые предпочитают реализовывать знаковый сдвиг вправо как логический, а не арифметический. GNU C может даже гарантировать, что сдвиг вправо со знаком является арифметическим (вместе с целыми числами, являющимися дополнением до 2). Это один из тех случаев, когда мы, вероятно, должны рассмотреть этот переносимый достаточно, потому что C++ допускает так много ошибок ...

Peter Cordes 14.06.2018 10:11

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