Я столкнулся с довольно своеобразной проблемой. Я работаю над компилятором для архитектуры, которая не поддерживает побитовые операции. Однако он обрабатывает подписанную 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), et.c.)
- Переходы (goto, for, et.c.)
Побитовые операции, которые я хочу поддерживать:
- Или (c = a | b)
- И (c = a и b)
- Xor (c = a ^ b)
- Левый сдвиг (c = a < b)
- Правый сдвиг (c = a → b)
- (Все целые числа подписаны, так что это проблема)
- Подписанный сдвиг (c = a → > b)
- Одно дополнение (a = ~ b)
- (уже найдено решение, см. ниже)
Обычно проблема - это наоборот; как добиться арифметической оптимизации с помощью побитовых хаков. Однако не в этом случае.
В этой архитектуре записываемая память очень скудна, следовательно, требуется побитовая операция. Поразрядные функции сами по себе не должны использовать много временных переменных. Тем не менее, постоянные данные только для чтения и память команд в изобилии. Обратите внимание также на то, что прыжки и ветки не дорогие, и все данные легко кэшируются. Скачки стоят половину циклов, как это делают инструкции арифметики (включая загрузку/хранение). Другими словами, все вышеперечисленные поддерживаемые функции стоят вдвое больше циклов одного скачка.
Некоторые мысли, которые могут помочь:
Я понял, что вы можете сделать одно дополнение (отрицание бит) следующим кодом:
// Bitwise one complement
b = ~a;
// Arithmetic one 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 and 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;
}