Глупая идея № 1: Таблица поиска. Это не может работать в 16-битном реальном режиме. Даже целого сегмента размером 64 КБ для таблицы недостаточно; нам нужно вдвое больше, чтобы иметь возможность искать 2-байтовый результат для любого возможного 16-битного значения.
Мы могли бы легко сделать это с 32-битной адресацией, например xor ebx, ebx
/ mov bx, ax
/ mov bx, [table + ebx*2]
, если вы можете оправдать 128 кБ данных таблицы. : P
Полностью в рамках правил вы можете построить таблицу в стеке в 32-битном или 64-битном режиме с помощью sub esp, 1<<17
и сохранить данные с помощью mov word [esp+0], 0
/ mov word [esp + 2], 1
/ et c. Полностью развернут, без зацикливания, так что около 256 кБ машинного кода. Но, опять же, это не работает в реальном режиме и является полной шуткой для эффективности.
Мы можем использовать shenanigans с частичным регистром x86, чтобы изолировать знаковый бит как целое число 0/1:
xor dx, dx ; DX = 0
mov dl, ah ; DX = AX>>8 (zero extended)
add dx, dx ; DX <<= 1 shifts the sign bit alone into DH
mov dl, dh
mov dh, 0 ; DX = (AX<0) = sign bit of AX zero extended to 16-bit
neg dx ; DX = 0 or -1
Или последние 3 инструкции могут быть оптимизированы до 2
neg dh ; 0 or -1 according to sign bit of AX
mov dl, dh ; duplicate to the full DX = 0 or -1
Джекпот; у нас есть значение sar ax,15
или cwd
, в котором есть все биты 0 или все биты 1, передающие знаковый бит AX, готовые к использованию с идентификатором дополнения 2 ( Как доказать, что оператор C - x, ~ x + 1 и ~ (x-1) дают одинаковые результаты? ), как используют компиляторы (https://godbolt.org/z/n3yoUp).
Обычно вы используете xor ax, dx
/ sub ax, dx
для изменения исходного значения.
Раньше я думал, что задача потребовала от вас избегать изменения любых других регистров, в противном случае ограничение на оставление AX без изменений является тривиальным и не стоит сделать часть проблемы. Но я не думаю, что это возможно без дополнительного места в памяти или другого регистра. Редактирование разъяснило, что в этом нет необходимости.
mov bx, ax
xor bx, dx ; ~x or x
sub bx, dx ; ~x + 1 or x
XOR с -1
переворачивает все биты, как НЕ. XOR с 0
не используется.
SUB с шагом -1
на 1, SUB с 0
не используется. (0
является элементом идентификации для сложения и xor.)
Таким образом, это условно применяет идентичность дополнения 2 -x = ~x + 1
.
PS: Мне потребовалось несколько минут головы Я думаю об этом, исключаю любые подходы с полным регистром, и я очень знаком с x86 и довольно хорошо разбираюсь с битовыми манипуляциями, например, пишу ответы codegolf.SE в машинном коде x86 и делаю не тривиальные вещи с SIMD. IMO, это сложный и сложный вызов.
Кроме того, вы никогда не захотите писать такой код в реальной жизни; cwd
или cdq
гораздо эффективнее, или для исходных регистров, отличных от AX, copy и sar
. Частичная регистрация даже приведет к остановке некоторых неиспользуемых исполнительных процессоров, таких как Intel PPro и Nehalem.
Например, на проводнике компилятора Godbolt для этого источника :
unsigned absval(int x) {
return x<0 ? 0U - x : x;
}
Использование возвращаемого значения без знака позволяет нам избежать неопределенного поведения переполнения со знаком со знаком для наиболее отрицательного целого числа дополнения 2. (-INT_MIN
- неопределенное поведение). Я думаю, что способ, которым я написал, на самом деле основывается на реализации C, являющейся дополнением к 2, хотя, потому что 0U - x
преобразует x
в unsigned для соответствия другой стороне перед , используя его как операнд для двоичного кода -
. Или, может быть, это то, что мы хотим, чтобы беззнаковый 0U-x
производил 0x8000
из входа 0x8000
(для 16-битного целого).
G CC делает это для установки EAX = abs ( EDI) (x86-64 System V соглашение о вызовах).
mov eax, edi
cdq ; sign-extend EAX into EDX:EAX
xor eax, edx
sub eax, edx
ret
clang делает это для x86-64, используя условное перемещение, которое считывает флаги из NEG:
mov eax, edi
neg eax ; 0 - x
cmovl eax, edi ; copy the original if 0 was < x
ret
это будет на некоторых процессорах более эффективны:
; shorter critical path on CPUs where mov is not zero latency
xor eax, eax
sub eax, edi ; 0 - x
cmovl eax, edi ; copy the original if 0 was < x
ret
Sandybridge устраняет обнуление по оси X, но не перемещает, а для процессоров, которые не устраняют mov
, это сокращает критический путь. mov eax,edi
находится на критическом пути, но xor
- ноль. Или мы могли бы сделать mov eax, edi
/ neg edi
/ cmovnl eax, edi
, чтобы снова разрешить параллельную работу MOV и NEG.
CMOV - это команда из двух операций на процессорах Intel до Broadwell. (CMOVA и CMOVBE по-прежнему являются 2 мопами на текущем Intel, потому что они читают CF и ZF, которые переименованы отдельно в разных группах. Другие 1 моп)