¿Por qué necesitamos rotar y cambiar en lenguaje ensamblador?

Técnicamente, no lo hacemos. La pragmática dicta lo contrario.

Tenemos instrucciones en el conjunto de instrucciones porque

  • En algún momento en el pasado, algún ingeniero tenía la opinión de que podría ser útil. En los “viejos” días, a menudo esto era envidia del conjunto de instrucciones: “Bueno, la computadora X tiene esa instrucción, por lo que nuestra nueva computadora Y también debería tener algo así”. Este tipo de pensamiento fue generalizado en los primeros días cuando nadie midió el rendimiento con cuidado, o lo cambió por el costo.
  • En un momento más reciente en el pasado, un equipo de análisis de rendimiento descubrió que ciertos algoritmos, considerados clave por el personal de marketing, funcionarían más rápido si existiera alguna instrucción especial.

De hecho, puede sobrevivir con solo una instrucción: restar A de B, saltar a C si es negativo.
Puede implementar todo lo demás usando solo esto. Entonces, cualquier instrucción real adicional sería simplemente salsa.

Para máquinas más realistas, es difícil evitar LOAD, STORE, CMP, ADD, SUB y AND, condicional. Es fácil “desplazarse a la izquierda 1” agregando un valor a sí mismo; “Desplazar a la izquierda N” haciendo eso N veces.

Desplazar a la derecha es más difícil, pero siempre puede dividir un valor en fragmentos de M bits (Y con 2 ^ M-1) y luego buscar ese valor de M bits en una tabla que contiene la versión desplazada a la derecha. Debe hacer esto para cada uno de los fragmentos de M bits que forman el operando del cambio, y volver a ensamblar los resultados (¡haciendo el desplazamiento a la izquierda!). Torpe, sí, factible sí.

Por lo tanto, en realidad no necesitamos estas instrucciones para que las computadoras funcionen. Pero puede ver que tenerlos puede producir respuestas más rápidas cuando las necesitamos.

¿Cuándo podríamos necesitar estos? Bueno, cualquier circunstancia que implique multiplicar o dividir valores, en una máquina que no tiene instrucciones de multiplicar y dividir sofisticadas ya integradas. Puede codificar la multiplicación y la división como un algoritmo que se desplaza hacia la izquierda o hacia la derecha, utilizando algoritmos de multiplicación y división que aprendió en el 4to grado, adaptados de decimal a binario. (Detalles dejados al lector).

Entonces, estas (o cualquier otra instrucción adicional) están ahí para hacer que los programas de ensamblaje sean realmente más pequeños y más eficientes (o para instrucciones mucho más antiguas como Agregar Decimal, imaginado por ingenieros como más pequeño).

Como cuestión práctica, el conjunto de instrucciones que forman el núcleo de la mayoría de las máquinas RISC son las que compran gran parte del rendimiento. La mayoría del resto compra rendimiento adicional en circunstancias especiales, generalmente a un costo bastante alto en transistores adicionales. Pero en estos días, los transistores son baratos. Espere que los proveedores de CPU sigan inventando nuevas instrucciones útiles, a veces.

El desplazamiento se usa generalmente para multiplicación y división por 2, verifique el multiplicador binario y la división binaria, mientras que el desplazamiento aritmético a la izquierda se usa para números con signo, mientras que el cambio lógico se realiza para operaciones sin signo.

La rotación de bits se puede usar para muchas cosas, existe la rotación a través del transporte y la rotación pura. Con carry es, por ejemplo, útil para imprimir la representación binaria de un número, por lo que tiene una versión no destructiva de sacar todos los bits y llevarlos al carry.

O puede cambiar todos los bits, significa de mayor a menor o puede usarlo para algoritmos de cifrado como AES o DES se hizo con eso, solo verifique el S-box y hay muchas más aplicaciones, como la generación de pseudo números y así en.

La rotación con carry es más o menos el método de cómo rotar un conjunto completo de palabras, o puede usarlo para flujos de bits, etc. La rotación con carry es la variante más aplicable, razón por la cual la encontrará en los procesadores primitivos, mientras que la rotación sin carry es más importante para los procesadores más avanzados.

; rotar bits sobre diez bytes
; cpu 6502
; use xa para ensamblar (sudo apt-get install xa)

rotl_10b:
ldx # 10; número de bytes para manipular
ldy # 0; índice al bytefield
clc; primer turno en cero

repetir:
datos lda, y; desplazamiento
rol
datos sta, y
iny; siguiente byte en campo
dex; disminuir la cuenta regresiva de diez
bne repita; hasta que todos los bytes terminen

datos lda; ahora agregue el bit superior al byte más bajo
adc # 0; agregar carry
datos sta
rts; y volver

datos:
.byt 1,2,3,4,5,6,7,8,9,10; little endian bajo a alto
.fin

Las aplicaciones son muchas, desde gráficos, hasta manipulaciones de E / S en serie de “bit-banging” de pines de E / S para comunicación a lógica, codificación de morse o algo así. Un punto representará un corto, un 1 a largo y puede almacenar la longitud del símbolo en otros tres bits. O almacenar códigos de cifrado de Huffman en la memoria, donde estamos nuevamente en flujos de bits.

A menudo, las aplicaciones de desplazamiento y rotaciones son intercambiables, pero son diferentes y debido a que es una de las cosas en las que muchos procesadores eran diferentes en velocidad, era uno de los campos de batalla de la optimización, así como entre arquitecturas y entre compiladores.

La manipulación inicial de bits fue realmente pésima en C y me sorprendería si fuera realmente bueno hoy.

Un ASL es lo mismo que un ROL con carry, mientras que su carry siempre está despejado, significa cambio en cero. Un ASR es lo mismo que un ROR mientras que el carry permanece establecido en el valor del bit más alto. Con LSR y LSL, necesitaría limpiar el equipaje de forma permanente.

Como puede ver, los cambios son formas especializadas de la rotación con carry, mientras que no tiene que lidiar con el restablecimiento permanente o la configuración del bit de cambio. Lo que significa una instrucción menos, lo que significa el doble de velocidad.

Esa es la razón por la cual no solo existen rotaciones con carry.

Otro ejemplo de cómo combinar ambos comandos:

; dividir aritméticamente por dos una palabra firmada
; cpu 6502
; use xa para ensamblar (sudo apt-get install xa)

divide_two:
Datos lda + 1; desplazamiento aritmético a la derecha = dividir entre 2 con signo
asr; el signo permanece
sta data + 1
datos lda; continúe con el byte inferior de la palabra
ror; el transporte del byte alto se desplazó
datos sta
rts

datos:
.word -653; little endian lowbyte, highbyte
.fin

Como puede ver, los comandos de desplazamiento se utilizan principalmente para el primer byte de la operación. Si es un desplazamiento a la derecha, el byte superior del valor, si es un desplazamiento a la izquierda, el byte bajo. Después de eso, todos los otros bytes son operados por comandos de rotación.

Eso es principalmente por lo que tenemos operaciones de turno y operaciones de rotación: es para uso de varios bytes, comienza con un turno y continúa con las rotaciones. Es como sumar y restar sin llevar y con llevar. Empiezas sin y sigues con.

La multiplicación y división por una potencia de 2 son simplemente cambios a la izquierda y a la derecha. Tenga en cuenta que cualquiera de los dos podría funcionar más si se alimenta a través de los circuitos respectivos en la ALU (códigos de operación MUL o DIV).

En algunas arquitecturas, como ARM, hay muchas instrucciones inmediatas que codifican un valor en la instrucción real. Este valor ocupa bits menos de lo que se puede expresar en un registro. Un código de operación AArch64 ocupa 32 bits, por ejemplo. Cargar un inmediato en un registro de 64 bits restringiría el tamaño de dichos números a un valor <= el ancho de bits del campo inmediato, generalmente 16 bits como máximo. Sin embargo, tales instrucciones generalmente contienen un segundo campo que le permite cambiar el valor inmediato hasta 64 bits. Por lo tanto, lo que tomaría dos códigos de operación se puede reemplazar por 1.

La generación de números aleatorios tiene una buena distribución con el Registro de desplazamiento de retroalimentación lineal que se basa en manipulaciones de bits.

La conversión de números de coma flotante IEEE ay desde la representación de enteros requiere manipulación de bits.

El código de ensamblaje auto modificable necesita manipulación de bits para configurar las instrucciones.

Las bibliotecas arbitrarias de precisión deben prestar atención a los bits.

Los registros mapeados de memoria a menudo tienen campos de bits que deben establecerse individualmente. Debe usar máscaras de bits y turnos para establecer sus valores. Por ejemplo, aquí hay una macro que uso para establecer campos:

#define GET_DATA_FIELD (d, m, s) ((d & m) >> s)
#define SET_DATA_FIELD (d, f, m, s) ((d & ~ m) | ((f << s) & m))

d = datos

m = máscara

s = cambio

f = campo

Supongamos que quisiera establecer los bits 5: 3 a 3′b101 (que es 0x5 en hexadecimal):

uint32_t register_value = read_register (MEMORY_LOCATION);
valor_registro = SET_DATA_FIELD (valor_registro, 0x5, 0x38, 3);
write_register (register_value, MEMORY_LOCATION);

También puede verificar la paridad de un número a través de:

odd_parity = número & 0x1;

No estoy seguro si está preguntando “qué” o “por qué”. Reformule su pregunta para aclararla.

Los bits de desplazamiento y rotación son útiles para varios propósitos de “giro de bits” de bajo nivel, como la compresión y descompresión de datos, el cifrado y la lectura y control de registros de hardware del dispositivo. Estoy seguro de que existen otros propósitos que se me escapan en este momento.

La rotación y el desplazamiento son muy triviales para obtener en hardware: todo lo que tiene que hacer es conectar el bit de salida de un registro en el bit de entrada adyacente y dar un tic del reloj, y tiene una multiplicación de un disparo por dos (o 4, 8 … dependiendo de cuántos bits agrupe) a un costo de hardware cero.

Sumar / restar requiere una red combinatoria de reacción de 2 a 4 compuertas, y la división múltiple retira ya sea una cascada de sumador desplazado o una repetición de sumar y desplazar.

Si pensamos en la computadora como “calculadoras” CS, tendemos a pensar en IF (! SUB ((A), (B)) GOTO (eso es lo primitivo de una máquina de Turing), pero ¿pensamos en términos de electrónica digital? circuitos sincrónicos, SHIFT & ADD son las primitivas de hardware que componen la aritmética.