Translate

sábado, 8 de abril de 2017

Assembler, Discución

Ya hace un tiempo publique un hilo en retrowiki.es ya cerrado, que hablaba de las incomodidades del Assembler y cómo mejorarlo, ya que yo pienso que debería o podría ser más estructurado y más ameno para los humanos y no tan fácil para el compilador o editor.

Decia algo asi:
Notapor luiscoco » 26 10 14 00:04
-bRick Hola amigos, hace mucho que le doy la vueltas en la cabeza con este tema recurrente, y ahora recientemente, me he enfrascado más con Assembler.
Como soy rebelde, y nada conformista, tengo pensamientos radicales y le busco el porque a las cosas y a esto no se lo encuentro el porque.

Veamos: al principio de los tiempos del lenguaje de maquina, a unos ingenieros se les ocurrió una nomenclatura para nombrar las instrucciones numéricas (lenguaje de máquina), que usaban sus procesadores, y nada, le dieron nombres y se quedaron tan felices, y a comer perdices, hasta nuestros tiempos y después de 300 generaciones de CPUS, aún se usa lo mismo, pues por mi, se podían haber quedado quietos, porque para mi no lo hicieron del todo bien.

Se que eran ingenieros de electrónica y no sabrían mucho de lenguaje y tal, pero de matemáticas deberían saber, así que no hay escusa.

Expliquen lo siguiente:

Porque si un niño de 13 años sabe que lo que significa A = 27, a estos ingenieros se les ocurrió que era mejor decir LDA 27, no lo se pero trastocaron todo, ademas, tienes que aprender un lenguaje nuevo, que aunque no es muy difícil, hay que aprenderlo, y no usaron una notación tan obvia como A = 27

Cuando escribimos assembler siempre ponemos en los comentarios cosas como A = 0 o cargar en A el valor de tal cosa  fíjense en todos los assemblers

Si usaran esta notación matemática todo seria mas fácil, fíjense en la ultima columna NOTAS de este set de Z80 aquí listado completo
 MASS.ZIP
(5.85 KiB) 97 veces
y la pagina de donde sale http://wiki.speccy.org/cursos/ensamblador/lenguaje_5
Siempre hay que estar comentando, como en esa ultima columna

Se podrian escribir cosas como en basic
A = $27: B = 45: y asi sin tener que comentar tanto, casi se podria hacer estructurado

Se que dirán que ADD A, 27 es mas fácil que A = A + 27, pero este ultimo no tienes que aprenderlo ya lo sabes de antemano y total para la maquina es igual

  • Podríamos poner varios comandos en una linea y no interminables listas con comentarios,
  • Se podría poner indentados,  los loops
  • GOTO aunque no gustan, pero serian visibles
  • hasta un niño lo entendería
CÓDIGO: SELECCIONAR TODO
--------------+----+---+------+------------+---------------------+-----------------------
|Mnemonic     |Clck|Siz|SZHPNC|  OP-Code   |    Description      |        Notes         |
--------------+----+---+------+------------+---------------------+-----------------------
|ADC A,r      | 4  | 1 |***V0*|88+rb       |Add with Carry       |A=A+s+CY    |A=A+?+C
|ADD A,r      | 4  | 1 |***V0*|80+rb       |Add (8-bit)          |A=A+s       |A=A+?
|AND r        | 4  | 1 |***P00|A0+rb       |Logical AND          |A=A&s       |A=A AND ?
|CALL NN      | 17 | 3 |------|CD XX XX    |Unconditional Call   |-(SP)=PC,PC=nn        |
|CPL          | 4  | 1 |--1-1-|2F          |Complement           |A=~A        |A=~A
|DEC A        | 4  | 1 |***V1-|3D          |Decrement (8-bit)    |s=s-1       |s=s-1
|EX (SP),HL   | 19 | 1 |------|E3          |Exchange             |(SP)<->HL             |
|JP $NN       | 10 | 3 |------|C3 XX XX    |Unconditional Jump   |PC=nn                 |
|JR $N+2      | 12 | 2 |------|18 XX       |Relative Jump        |PC=PC+e               |
|LD I,A       | 9  | 2 |------|ED 47       |Load*                |dst=src 
|NEG          | 8  | 2 |***V1*|ED 44       |Negate               |A=-A                  |
|OR r         | 4  | 1 |***P00|B0+rb       |Logical inclusive OR |A=Avs                 |
|RET          | 10 | 1 |------|C9          |Return               |PC=(SP)+              |
|SCF          | 4  | 1 |--0-01|37          |Set Carry Flag       |CY=1                  |
|SUB r        | 4  | 1 |***V1*|90+rb       |Subtract             |A=A-s                 |
|XOR r        | 4  | 1 |***P00|A8+rb       |Logical Exclusive OR |A=Axs                 |


Veamos este trozo
L_6D31: 
 CALL L_6E97   ; 6D31 CD 97 6E                 * Busca el final del Buffer de entradas
 CP $D0        ; 6D34 FE D0     A = $D0?       * Compara con $D0
 JR Z, L_6DA2  ; 6D36 28 6A     Z = 1          * Si es cero salta a L_6DA2
 CP $90        ; 6D38 FE 90     A = $90?       * Compara con $90
 JR NZ, L_6D6C ; 6D3A 20 30     Z = 0?         * si no es cero salta a L_6D6C
 LD A, B       ; 6D3C 78        A = B          * A = $40
 AND $0F       ; 6D3D E6 0F     A = A AND $0F  * 
 OR C          ; 6D3F B1        A = A OR C     * A = A OR 5
 JR NZ, L_6D6C ; 6D40 20 2A     Z = 0?         * Si no es cero salta a L_6D6CL_6D6C
 LD A, (L_B71B); 6D42 3A 1B B7  A = (L_B71B)   * Toma un dato de DATABLK1
 AND A         ; 6D45 A7        A = A AND A    * Adecua flags
 JR NZ, L_6D4E ; 6D46 20 06     Z = 0          * Si no es cero salta a L_6D4E
 INC A         ; 6D48 3C        A = A + 1      * Es cero, Incrementa A, A = 1
 LD (L_B71B), A; 6D49 32 1B B7  (L_B71B) = A   * (L_B71B) = 1, inicializa dato en DATABLK1
 JR L_6D6C     ; 6D4C 18 1E                    * Salta a L_6D6C


Yo lo escribiría así:
CÓDIGO: SELECCIONAR TODO
L_6D31: PC = L_6E97
        A = $D0?: IF Z=1 GOTO  L_6DA2
        A = $90? : IF Z=0 GOTO L_6D6C
        A = B: A = A AND $0F: A = A OR 5: IF Z=0 GOTO L_6D6C
        A = (L_B71B): A = A AND A: IF Z=0 GOTO L_6D4E
        A = A + 1: (L_B71B) = A: GOTO L_6D6C   

O ASI
CÓDIGO: SELECCIONAR TODO
L_6D31: PC = L_6E97
             A = $D0?
             IF Z=1 GOTO  L_6DA2
             A = $90?
             IF Z=0 GOTO L_6D6C
             A = B
             A = A AND $0F
             A = A OR 5
             IF Z=0 GOTO L_6D6C
             A = (L_B71B)
             A = A AND A
             IF Z=0 GOTO L_6D4E
             A = A + 1
            (L_B71B) = A
            GOTO L_6D6C   

Los GOTO L_6D6C, Pueden ser PC = L_6D6C

No es que sea perfecto pero el 70% puede ser así, algunos comandos serian de la forma tradicional.

Creo que es digno de estudiarse y no simplemente seguir a todos como borregos, desde hace 30 años

Fácilmente puedo hacer un compilador que entienda los dos formatos, llamemosles (tradicional y matemático)

luiscoco
Mensajes: 1880
Registrado: 15 05 11 04:23
Ubicación: Venezuela


Profundicemos un poco más
Los IF son solo con GOTO ya que así es el Lenguaje de Máquina
IF Z = 0 GOTO XXXX
IF Z = 1 GOTO XXXX
IF C = 1 GOTO XXXX
IF C = 0 GOTO XXXX
IF P = 1 GOTO XXXX
IF P = 0 GOTO XXXX
IF N = 1 GOTO XXXX
IF N = 0 GOTO XXXX
tal vez los > y < o mayor e igual se podrían contemplar
tambien
IF N = 0 RETURN (retornos condicionales o RET N, podría quedarse como están


Las asignaciones
A = 22
B = A

Las comparaciones
A-34 sin colocar el resultado en ningún lado o A = 34

Los saltos
GOTO Label
o
PC = XXXX
JP podría seguir igual

Los incrementos y decrementos
A = A + 1 (con espacios en medio o no)
A = A - 1
o mas corto
A+=1
A+
A++



Notapor mcleod_ideafix » 26 10 14 02:10
luis46coco escribió:Expliquen lo siguiente:

Porque si un niño de 13 años sabe que lo que significa A = 27, a estos ingenieros se les ocurrió que era mejor decir LDA 27, no lo se pero trastocaron todo, ademas, tienes que aprender un lenguaje nuevo, que aunque no es muy difícil, hay que aprenderlo, y no usaron una notación tan obvia como A = 27


Primero, porque no es obvio, aunque para ti lo sea.
Un niño de 13 años, sin formación previa en programación, interpretará A = 27 como una igualdad (una ecuación) trivial, en la que la incógnita A resulta ser 27. No lo interpretará como una asignación. De hecho, algo como:
A = A + 1
Lo confundirá aún más, ya que esto en matemáticas es una ecuación en la que la incógnita es A, y para colmo, una ecuación que no tiene solución.
De hecho, algunos lenguajes de programación usan otra notación para las asignaciones, y que de esa forma no se confunda con la igualdad matemática. Pascal, el más conocido, usa := para la asignación.
En C, el hecho de haber usado = en las asignaciones, hace que la igualdad matemática tenga que expresarse con otro símbolo, == (lo que da muchos quebraderos de cabeza a los que empiezan con la programación en C)

Segundo, porque tu notación dificulta la escritura de un parser que convierta el texto del lenguaje ensamblador a código máquina.
Hoy día no es gran cosa hacer un parser que coja tu propuesta de lenguaje ensamblador y la convierta en un ensamblador hecho y derecho, pero piensa en aquella época, en la que probablemente los ensambladores tuvieran que escribirse "a mano". En ese sentido, una sintaxis rígida (nmemotécnico + operando detino + operando(s) fuente(s) era la opción más sencilla para parsear el texto. Para muchas arquitecturas, el nmemotécnico ya da parte del código máquina, rellenándose el resto con el código que tenga asignado cada registro, o el valor del operando inmediato. Así, el código máquina se va generando a medida que se lee el texto.
De hecho, con los primeros ensambladores había que dejar una serie de espacios en cada línea de código (8 creo) para hacer sitio para la etiqueta, y si no se dejaban y se empezaba a escribir el nmemotécnico en la columna 1, el parser podía interpretarlo como una etiqueta y no como una instrucción.
Assembler no es el único lenguaje con este tipo de rigidez: otro dinosaurio como el COBOL también tiene reglas estrictas sobre dónde deben empezar las sentencias (linea 8 para las DIVISION, línea 12 para las SECTION, poner un asterisco en la línea 7 para empezar un comentario, etc).

Algo como lo que propones...

CÓDIGO: SELECCIONAR TODO
A = $D0
B = 0?


Necesita de un parser LALR para poder compilarse: cuando el parser se encuentra con = ¿lo debe interpretar como una asignación o una pregunta? Eso no se sabe hasta parsear el final de la línea, y para ello se necesita una pila de simbolos, complicación extra en el parser, más memoria, etc. De hecho, no fue hasta que Donald Knuth "inventara" la atribución de gramáticas que fue posible escribir parsers de forma más sencilla (y aparecieron las herramientas lex y yacc para construir compiladores)

Tercero: porque tu propuesta no soluciona del todo el problema de la (presunta) ilegibilidad del ensamblador, y encima añade trabas. Si bien para instrucciones de carga y almacenamiento, la notación análoga a las matemáticas ayuda, ¿qué pasa con el resto de instrucciones que no definen una operación matemática o lógica? ¿Cómo actualizas a un lenguaje moderno instrucciones del Z80 tal como EXX, DI, EI, IM 2, etc?
Y respecto a las trabas: en una notación más universal tú podrías hacer algo como esto (un programa que suma números del 1 al 10):

CÓDIGO: SELECCIONAR TODO
A = 0
C = 1
bucle:
A = A + C
C = C + 1
C != 11? bucle


Pero esto, en un Z80, origina una colisión en el uso de registros: veamos la posible traducción:
CÓDIGO: SELECCIONAR TODO
LD A,0   ; el compilador podría elegir XOR A para hacer lo mismo: un byte menos y 3 ciclos menos.
LD C,0
bucle:
ADD A,C
INC C
CP C,11   ; ouch!!!
JR NZ,bucle

El problema es que en el Z80 sólo se puede comparar con el registro A, así que sería necesario guardar el valor actual de ese registro en otro sitio (la pila, memoria, el juego alternativo, etc) lo cual hace que el programa traducido no sea linea a linea equivalente a lo que tú has introducido, y por tanto ya no pueda ser considerado ensamblador, sino algo de más alto nivel. El compilador de este lenguaje tendría que elegir qué estrategia seguir para resolver este pequeño conflicto. Tu código máquina ya no sería 100% un reflejo de lo que has escrito en el código fuente, y cosas como el cálculo de ciclos de reloj que hace un programador en ensamblador para saber cuánto tarda su rutina, ya no sería fiable.

Algo parecido hubiera pasado si en lugar de preguntar si C es distinto de 11, voy y pregunto si C es igual o menor que 10. En ese caso, la traducción sería (supuesto que existiera la instrucción CP C,10):

CÓDIGO: SELECCIONAR TODO
...
INC C
CP C,10
JR C,bucle  ;salto si C es de 1 a 9
JR C,bucle  ;salto si C es 10


Aunque un programador en ensamblador sabe que preguntar si C es igual o menor que 10 es lo mismo que preguntar si C es menor estricto que 11, o en este ejemplo en donde sabemos cómo evoluciona C, preguntar si C es distinto de 11. Estas cosas pueden ser detectadas por un compilador moderno que realice optimización de código, pero no en aquella época, en la que la traducción más probable es la que he puesto, que NO es la mejor, aunque un programador humano sí que escribiría el código óptimo (y sabría que ese código es "el que es" y no uno que infiere el ensamblador de forma automática)

Por último, comentar que poner en un listado en assembler comentarios tales como "poner A = 0" al lado de una instrucción tal como LD A,0 del Z80 es totalmente redundante, y es un ejemplo de un comentario inútil. Mucho más útil es decir por qué pones A a 0. Algo como: "inicializar contador de bits" como comentario a LD A,0 da mucha más información sobre lo que hace esta instrucción.

De todas formas, no eres el primero que pensó que assembler es demasiado poco intuitivo, y así Gary Kindall inventó el PL/M, que incorpora alguna de las características que propones, y aún más allá (claro que por la época de Kindall ya existían los parsers LALR y la atribución de gramáticas de Knuth)



Notapor antoniovillena » 26 10 14 02:26

Los nemónicos son más fáciles de recordar que las fórmulas matemáticas. Con los nemónicos quitas redundancia, ¿no te das cuenta que todas las instrucciones tienen el signo igual? En las operaciones lógicas tienes siempre la misma estructura A = A AND algo. Aparte de que es más laborioso de escribir mete mucha simbología. En un lenguaje de alto nivel donde tienes total libertad sí tiene sentido poner A:= B AND C, pero en ensamblador la máquina está muy limitada. No es lo mismo A= A+1 que A= A+8, son instrucciones distintas, es más B= B+8 no existe.

En ensamblador es muy importante llegar al nivel en que conoces todas las instrucciones, incluso las que se usan muy raramente. Con nemónicos es muy sencillo porque tu cerebro las reconoce fácilmente y cuando ve algo nuevo sabe seguro que no lo ha visto antes y lo asimila más rápido. Por ponerte un ejemplo, LDDR. Sabes que es la primera vez que la ves pero antes has visto LDD ó LDIR por lo que te puedes imaginar lo que hace. Cuando lleves un tiempo te darás cuenta que sólo hay 4 instrucciones de este tipo. Hacer lo mismo con cosas como (de++)= (hl++), bc--, no sé lo veo muy complejo.

No sé, es difícil de explicar pero creo que los que los diseñaron no lo hicieron tan mal. Y si crees que tu sistema es más práctico puedes hacerte un ensamblador para tí mismo, es la ventaja de ser programador. A mí por ejemplo no me gusta el diseño del teclado y tengo uno mapeado según mis gustos. Otra cosa muy distinta es convencer a los demás de que tu sistema es mejor pero te puedes aplicar la norma esa de "si quieres cambiar el mundo, empieza por tí mismo".



Notapor luiscoco » 26 10 14 15:58
Pero a que te encantaria algo como esto, al menos los loops visibles

Loops.PNG