Я столкнулся с довольно своеобразной проблемой. Я работаю над компилятором для архитектуры, которая не поддерживает побитовые операции. Однако он обрабатывает знаковую 16-битную целочисленную арифметику, и мне было интересно, можно ли реализовать побитовые операции, используя только:
- Дополнение (c = a + b)
- Вычитание (c = a - b)
- Деление (c = a / b)
- Умножение (c = a * b)
- Модуль упругости (c = a% b)
- Минимум (c = min (a, b))
- Максимум (c = max (a, b))
- Сравнения (c = (a ‹b), c = (a == b), c = (a‹ = b) и т. д.)
- Переходы (goto, для и т. д.)
Побитовые операции, которые я хочу поддерживать:
- Или (c = a | b)
- И (c = a & b)
- Xor (c = a ^ b)
- Сдвиг влево (c = a ‹---------------- b)
- Shift вправо (c = a >> b)
- (Все целые числа подписаны, поэтому это проблема)
- Сдвиг со знаком (c = a >>> b)
- Дополнение к одному (a = ~ b)
- (Решение уже найдено, см. Ниже)
Обычно проблема заключается в обратном; как добиться арифметической оптимизации с помощью побитовых хаков. Однако не в этом случае.
Записываемой памяти очень мало в этой архитектуре, отсюда и необходимость в побитовых операциях. Сами поразрядные функции не должны использовать много временных переменных. Однако постоянной памяти данных и инструкций, предназначенной только для чтения, предостаточно. Также следует отметить, что переходы и переходы не являются дорогостоящими, и все данные легко кэшируются. Переходы обходятся вдвое меньше, чем арифметические инструкции (включая загрузку / сохранение). Другими словами, стоимость всех вышеупомянутых поддерживаемых функций в два раза больше, чем у одного прыжка.
Некоторые мысли, которые могут помочь:
Я понял, что вы можете выполнить дополнение (инвертировать биты) с помощью следующего кода:
// Bitwise one's complement
b = ~a;
// Arithmetic one's complement
b = -1 - a;
Я также помню старый прием со сдвигом при делении со степенью двойки, поэтому побитовый сдвиг можно выразить как:
// Bitwise left shift
b = a << 4;
// Arithmetic left shift
b = a * 16; // 2^4 = 16
// Signed right shift
b = a >>> 4;
// Arithmetic right shift
b = a / 16;
В отношении остальных побитовых операций я немного не понимаю. Я бы хотел, чтобы архитекторы этой архитектуры предоставили битовые операции.
Я также хотел бы знать, есть ли быстрый / простой способ вычисления степени двойки (для операций сдвига) без использования таблицы данных памяти. Наивным решением было бы перейти в поле умножения:
b = 1;
switch (a)
{
case 15: b = b * 2;
case 14: b = b * 2;
// ... exploting fallthrough (instruction memory is magnitudes larger)
case 2: b = b * 2;
case 1: b = b * 2;
}
Или подход Set & Jump:
switch (a)
{
case 15: b = 32768; break;
case 14: b = 16384; break;
// ... exploiting the fact that a jump is faster than one additional mul
// at the cost of doubling the instruction memory footprint.
case 2: b = 4; break;
case 1: b = 2; break;
}
a*sel + b*(1-sel)
вместоsel ? a : b
. - person greggo   schedule 05.02.2015