Jupiter Ace Forth: Utilidades II

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 17 Sep 2022 15:51

Último mensaje de la página anterior:

Otra palabra:

2OVER ( d2,d1 - d2,d1,d2 ) o ( n4,n3, n2,n1 - n4,n3, n2,n1, n4,n3 )

Coloca en la pila una copia del segundo entero doble o copia al principio de la pila el tercer y cuarto entero sencillo.

En Forth se definiría así:

: 2OVER ( d2,d1 - d2,d1,d2)
4 PICK 4 PICK
;

La versión de la librería de Dutra hace un uso parcial de la definición en ROM de ROLL.

Código: Seleccionar todo

; 2OVER ( d2,d1 - d2,d1,d2 ) o ( n4,n3, n2,n1 - n4,n3, n2,n1, n4,n3 )

; La subrutina $095B forma parte del código de ROLL en la ROM.

            XOR     A           ; Pone a 0 el Carry Flag porque en la subrutiena que llamamos a continuación
                                ; se realiza un SBC y el Carry debe estar a 0. La segunda vez que llamamos
                                ; a la subrutina, como el Carry no habrá cambiado, ya no hace falta borralo.
           
            LD      BC,0008     ; Copia n4 arriba de la pila (encima de n1)
            CALL    095B
           
            LD      BC,0008     ; Copia n3 arriba de la pila (encima de n4)
            CALL    095B
           
            JP      (IY)        ; Vuelta a Forth


La subrutina que llamamos dos veces con CALL se puede ver en el desensamblado de la ROM del Jupiter Ace, del que reproduzco a continuación el fragmento que nos afecta:

L095B:  LD      HL,($3C3B)              ; SPARE
SBC HL,BC
PUSH HL
LD E,(HL)
INC HL
LD D,(HL)
RST 10H ; push word DE
POP HL
RET

La función de esta rutina es copiar un entero (2 bytes) al principio de la pila. El entero a copiar es el que tiene su Low byte en la dirección resultante de restar BC a (SPARE). La copia se realiza con RST 10h que ya se encarga él de actualizar SPARE.

El PUSH HL/POP HL es porque el resto del código del ROLL necesita el valor de la resta con SBC más adelante. Para la función descrita no hacen falta, pero nos lo hemos de "comer".

EDIT: Se podria acortar un byte cambiando el segundo LD BC,8 con LD C,8 como indicó @jltursan. El RST 10 no afecta a BC (solo usa el HL).

Esta versión es 3x veces más rápida que la versión Forth. Además es bastante corta, solo 1 byte más que la versión Forth gracias al uso de la subrutina.

Veamos una versión más rápida a costa de aumentar el tamaño en 3 bytes respecto a la Dutra

Código: Seleccionar todo

; 2OVER ( d2,d1 - d2,d1,d2 )

; Tenemos 4 enteros en la pila que forman dos dobles. Cada doble ocupa 4 bytes. En total ocupan 8 bytes. Lo que vamos
; a hacer es copiar los 4 bytes del segundo doble (el de abajo en la pila) por encima de final de la pila y luego
; actualizamos SPARE para reflejar la nueva situación.
        .LIST
       
        LD      BC,8        ; BC=8
        LD      HL,($3C3B)  ; HL=DE=primer byte después del final de la pila. SPARE: $3C3B
        LD      D,H         
        LD      E,L
       
        XOR     A           ; HL=dirección de inicio de los 4 bytes que componen el 2º doble.
        SBC     HL,BC
                           
        LD      C,4        ; Copiamos los 4 bytes del segundo doble encima de la pila
        LDIR
       
        LD      HL,$3C3B    ; (SPARE)=(SPARE)+4
        INC     (HL)
        INC     (HL)
        INC     (HL)
        INC     (HL)
       
        JP      (IY)        ; Vuelta a Forth

Esta versión es 1.7x más rápida que la Dutra y 5.1x más rápida que la versión en Forth. Pero a costa de aumentar el tamaño en 11 bytes!! respecto a la versión Forth, mientras que la Dutra solo aumenta 1 byte respecto a la Forth.

EDIT: (23-9-2022) También aquí el LD BC,4 se ha cambiado a LD C,4 y ganamos un byte.

También se podría cambiar el trozo de actualización del SPARE por un método con un ADD en vez de cuatro INC (HL), pero el aumento de tamaño se va a 14 bytes para un aumento de velocidad del 1%, por lo que no vale la pena.

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 17 Sep 2022 15:55

jltursan escribió:Recortale 2 de esos 3 bytes de más (y de paso la aceleras una pizca) cambiando los últimos:

Código: Seleccionar todo

LD BC,4

...

Código: Seleccionar todo

LD BC,8


por...

Código: Seleccionar todo

LD C,4

...

Código: Seleccionar todo

LD C,8


Muy buena ésta! Eso es hilar fino.

Hay que tener en cuenta que el fin último de Dutra con varias palabras de su librería era/es, una vez liberado suficiente espacio en la ROM del ACE, colocar cuantas más de ellas en la ROM, por lo que el ahorro de bytes para este fin es crucial. De ahí que a veces sacrifica rendimiento por tamaño. O al menos eso creo entender.

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 17 Sep 2022 16:30

jltursan escribió:Recortale 2 de esos 3 bytes de más (y de paso la aceleras una pizca) cambiando los últimos:

Código: Seleccionar todo

LD BC,4

...

Código: Seleccionar todo

LD BC,8


por...

Código: Seleccionar todo

LD C,4

...

Código: Seleccionar todo

LD C,8


Ya he aplicado tu modificación al caso de 2SWAP. En la siguiente palabra, 2ROT lo tendré en cuenta. Gracias @jltursan.

Si no me equivoco también se podría aplicar al segundo LD BC,n de la versión Dutra de 2OVER, ¿no?

NOTA: Los RST 10h y 18h solo afectan al registro HL
EDIT: además del DE

jltursan
Mensajes: 4878
Registrado: 20 Sep 2011 13:59
Ubicación: Madrid
Agradecido : 738 veces
Agradecimiento recibido: 1643 veces
Contactar:

Re: Jupiter Ace Forth: Utilidades II

Mensajepor jltursan » 17 Sep 2022 18:34

Si no me equivoco también se podría aplicar al segundo LD BC,n de la versión Dutra de 2OVER, ¿no?


Así es...:-)

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 17 Sep 2022 22:27

Otra palabra:

2ROT ( d3,d2,d1 - d2,d1,d3 )

Pasa el tercer doble de la pila a la primera posición.

En Forth se definiría así:

: 2ROT ( d3,d2,d1 - d2,d1,d3 )
6 ROLL 6 ROLL
;

La versión Dutra es: (Todos los números en hexadecimal)

Código: Seleccionar todo

; 2ROT ( d3,d2,d1 - d2,d1,d3 ) o ( n6,n5, n4,n3, n2,n1 - n4,n3, n2,n1, n6,n5 )

            RST  18     ; DE=n1
            PUSH DE
            RST  18     ; DE=n2
            PUSH DE
            RST  18     ; DE=n3
            PUSH DE
            RST  18     ; DE=n4
            PUSH DE
           
            EXX         ; Alternavite Registers
           
            CALL 084E   ; BC'=n5
            RST  18     ; DE'=n6
           
            EXX         ; Normal Registers
           
            POP  DE     ; TOS=n4
            RST  10
           
            POP  DE     ; TOS=n3
            RST  10
           
            POP  DE     ; TOS=n2
            RST  10
           
            POP  DE     ; TOS=n1
            RST  10
           
            EXX         ; Alternavite Registers
           
            RST  10     ; TOS=n6
           
            LD   E,C    ; TOS=n5
            LD   D,B
            RST  10
           
            EXX         ; Normal Registers
           
            JP   (IY)   ; Vuelta a Forth

Es una versión extendida de como lo hizo con 2SWAP: Saca de la pila con RST 18h los seis enteros guardándolos en Registros del Z80, incluidos los Registros alternativos y también en la pila de Retornos. A continuación los va reponiendo en la pila con RST 10h en el orden pertinente.

Esta versión (41 bytes) es 1.7x más rápida que la versión Forth (25 bytes). El incremento de tamaño es considerable: 16 bytes.

Veamos una versión alternativa más rápida y dos bytes más corta que la Dutra.

Código: Seleccionar todo

; 2ROT ( d3,d2,d1 - d2,d1,d3 ) o ( n6,n5, n4,n3, n2,n1 - n4,n3, n2,n1, n6,n5 )

; Tenemos 6 enteros en la pila que forman tres dobles. Cada doble ocupa 4 bytes. En total ocupan 12 bytes. Lo que
; vamos a hacer es copiar los 4 bytes del tercer doble (el de abajo en la pila) por encima de final de la pila y
; luego vamos a mover los últimos 12 bytes a la posición que ocupaban los 12 bytes originales.
; Con esto hemos hecho un 2ROT y además el valor almacenado en SPARE no cambia.

        LD      BC,$000C    ; BC=C (12)
        LD      HL,($3C3B)  ; HL=DE=primer byte después del final de la pila. SPARE: $3C3B
        LD      D,H         
        LD      E,L
       
        XOR     A           ; HL=dirección de inicio de los 4 bytes que componen el 2º doble.
        SBC     HL,BC
        PUSH    HL
                           
        LD      C,4         ; Copiamos los 4 bytes del tercer doble encima de la pila
        LDIR
       
        LD      C,$C        ; Movemos los 12 bytes de los tres dobles (ya rotados) a la posición original.
        POP     DE 
        LDIR

        JP      (IY)        ; Vuelta a Forth

Es una extensión de la versión alternativa usada para 2SWAP.

Esta versión (39 bytes) es 2.5x más rápida que la de Dutra (41 bytes) y 4.25x las versión Forth (25 bytes). Pero es 14 bytes mayor que la Forth.

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 18 Sep 2022 10:26

Las palabras 2ROLL y 2PICK no están en la librería de Dutra ni en el Ace Forth ni en el estándar FORTH-79.

En el manual del Jupiter Ace propone como ejercicio definirlas en Forth (pág. 92). Se podría hacer así:

: 2ROLL ( dn,dn-1, ···, d2,d1,n - dn-1, ···, d2,d1,dn )
DUP + DUP ROLL SWAP
ROLL SWAP
;

: 2PICK ( dn,···, d2,d1,n - dn,···,d2,d1,dn )
DUP + DUP 1+ PICK
SWAP PICK
;

Las versiones en código máquina serían similares a las versiones alternativas de 2ROT y 2OVER pues ambas son casos particulares de 2ROLL y de 2PICK respectivamente. Así 2ROT es equivalente a 3 2ROLL y 2OVER es equivalente a 2 2PICK.

Pero aquí hay una diferencia importante:

La nueva versión de 2ROLL tiene que comprobar que no nos "pasamos" de los límites de la pila de datos. En las versiones anteriores no lo comprobábamos pues NO era necesario. El peor de los casos era 2ROT que espera 6 enteros en la pila (3 dobles) que ocupan 12 bytes, justo el tamaño del colchón de seguridad que hay en el Ace Forth entre el final del diccionario y el inicio de la pila.

Pero ahora si usamos 2ROLL con valores mayores de 3 cuando no hay nada en la pila se puede corromper el diccionario (*). Una cosa es que nuestros cálculos con 2ROLL sean erróneos y no den error y otra muy distinta que corrompamos el diccionario. De ahí la necesidad de realizar la comprobación con la consiguiente pérdida de velocidad.

Con 2PICK no existe la posibilidad de corromper el diccionario, por lo que podemos obviar ésta comprobación.

También hemos de comprobar que el índice que le damos (el entero en la primera posición) no es cero. Antes no había ese problema, pues el índice era fijo y lo ponía el programa, no el usuario.

(*) En general si hacemos n 2ROLL habiendo m enteros sencillos en la pila se corromperá el diccionario si 2*n-m>6 .

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 18 Sep 2022 23:40

Vamos con 2ROLL

2ROLL ( dn,dn-1, ···, d2,d1,n - dn-1, ···, d2,d1,dn )

La versión Forth ya la he puesto antes. Hace uso de ROLL que no admite ni n=0 ni n<0 lo que implica que 2ROLL tampoco.

Pero resulta que ROLL se cuelga con n>0 si es suficientemente grande. En mi caso, con la pila vacía y HERE=22636 si hago 20000 ROLL se cuelga el Ace Forth. Las comprobaciones que hace NO son suficientes para evitar el cuelgue.

Si la pila es pequeña y meto un número muy grande, el test de límites de la pila falla y parece ser (no lo he comprobado) que al copiar el entero doble de abajo al tope de la pila se invade la pila de retornos. En fin, que he decidido hacer mi versión de 2ROLL de modo que:

  1. n sea distinto de 0 (como ROLL)
  2. n es un entero sin signo-> puede ser negativo (a diferencia de ROLL)
  3. Haga un chequeo más completo que evite tanto la corrupción del diccionario como el cuelgue del Ace Forth.

El punto tres lo hago así: Calculo la longitud de la pila en bytes (incluyendo los 12 bytes se segridad), llamémosla L, y compruebo que 4*n sea menor o igual que L.

Y esto, a diferencia de ROLL, lo hago "antes" de mover nada en memoria. Así se evita tanto la corrupción del diccionario como el cuelgue del Ace Forth.

Este es el código máquina de 2ROLL:

Código: Seleccionar todo

; 2ROLL ( d(n),d(n-1),···,d(1),n - d(n-1),···,d(1),d(n) )

; Tenemos 2*n enteros en la pila que forman n dobles y además en el TOS un entero "n". Cada doble ocupa 4 bytes.
; En total ocupan 4*n+2 bytes. Una vez sacado el índice "n" nos quedan 4*n bytes a manejar.
; Vamos copiar los 4 bytes del enésimo doble (el de abajo en la pila) por encima de final de la pila y
; luego vamos a mover los últimos 4*n bytes a la posición que ocupaban los 4*n bytes originales.
; Con esto hemos hecho un 2ROLL y además el valor almacenado en SPARE no cambia.

; Comprueba que "n" no valga 0 y  que el bloque de bytes a mover no supere el tamaño de la pila.
; Trata 4*n como entero sin signo, por lo que admite n negativos.

        CALL    $084E       ; BC=TOS
       
        LD      A,B         ; Si n=0 Error 7
        OR      C
        JR      NZ,CONT0
        RST     20H         ; Rutina de error con número del error igual al byte que le sigue (7)
        .DB     7
CONT0:     
        SLA     C           ; BC=4*n
        RL      B
        SLA     C           
        RL      B

        LD      HL,($3C3B)  ; HL=Tamaño en bytes de la pila=Longitud
        LD      DE,($3C37)  ;
        SBC     HL,DE       ;

        XOR     A           ; Si Longitud<4*n -> Se corrompería el diccionario -> ERROR 2 Stack underflow.
        SBC     HL,BC
        JP      C,$04D7     

        LD      HL,($3C3B)  ; HL=dirección de inicio de los 4 bytes que componen el enésimo doble.
        LD      D,H         ; DE=primer byte después del final de la pila. SPARE: $3C3B
        LD      E,L
        SBC     HL,BC
       
        PUSH    BC
        PUSH    HL
                           
        LD      BC,4        ; Copiamos los 4 bytes del enésimo doble encima de la pila
        LDIR                ; Al terminar LDIR->HL=Inicio del penúltimo doble en la pila
       
        POP     DE          ; DE=dirección de inicio de los 4 bytes que componen el enésimo doble.
        POP     BC          ; BC=4*n
        LDIR                ; Movemos los 4*n bytes de los n dobles (ya rotados) a la posición original.

        JP      (IY)        ; Vuelta a Forth

Esta versión (64 bytes) es 4.3x más rápida que la versión Forth (28 bytes). Pero también es mucho más grande.

NOTA:Los cronos se han hecho con n=6. A medida que n crece se hace menor el multiplicador(*). E.g. para n=12 es 3.5x

(*) A medida que aumenta n el multiplicador se irá acercando al límite de 2x. Esto se debe a que tanto el código de ROLL en la ROM como la versión en código máquina de 2ROLL utilizan LDIR que mueven 4*n bytes tanto en uno como en el otro. A medida que aumenta n aumenta el peso de este LDIR en el tiempo total de ejecución. Pero como la versión Forth de 2ROLL llama dos veces a ROLL, tardará el doble de tiempo que la versión de 2ROLL en código máquina.

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 19 Sep 2022 11:51

Vamos con 2PICK

2PICK ( dn,···,d1,n - dn,···,d1,dn )

La versión Forth ya la he puesto antes. Como llama a PICK y éste no admite ni n=0 ni n negativo, pues lo mismo con 2PICK.

En la versión en código máquina n no puede ser cero pero sí negativo pues n lo considera un entero sin signo.

Al no haber riesgo de sobreescribir el diccionario con PICK, solo se controlará que n sea distinto de cero. Así que ojo con el valor que se le da a n pues se podría sobreescribir la pila de retornos y colgarse el Ace Forth (al igual que con PICK).

Versión código máquina:

Código: Seleccionar todo

; 2PICK ( d(n),···,d(1),n - d(n),···,d(1),d(n) )

; Tenemos 2*n enteros en la pila que forman n dobles y además en el TOS un entero "n". Cada doble ocupa 4 bytes.
; En total ocupan 4*n+2 bytes. Una vez sacado el índice "n" nos quedan 4*n bytes a manejar.
; Vamos copiar los 4 bytes del enésimo doble (el de abajo en la pila) por encima de final de la pila y
; luego actulizamos SPARE $3C3B.
       
        CALL    $084E       ; BC=TOS
       
        LD      A,B         ; Si n=0 Error 7
        OR      C
        JR      NZ,CONT0
        RST     20H         ; Rutina de error con número del error igual al byte que le sigue (7)
        .DB     7
CONT0:     
        SLA     C           ; BC=4*n
        RL      B
        SLA     C           
        RL      B

        LD      HL,($3C3B)  ; HL=dirección de inicio de los 4 bytes que componen el enésimo doble.
        LD      D,H         ; DE=primer byte después del final de la pila. SPARE: $3C3B
        LD      E,L
        SBC     HL,BC
                           
        LD      BC,4        ; Copiamos los 4 bytes del enésimo doble encima de la pila
        LDIR                ; Al terminar LDIR->DE=Dirección del Primer byte después del final de la pila
       
        LD      ($3C3B),DE  ; Actualizamos SPARE
       
        JP      (IY)        ; Vuelta a Forth


Esta versión (47 bytes) es 7.6x más rápida que la versión Forth (28 bytes). El tiempo de ejecución es independiente del valor de n.

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 19 Sep 2022 12:27

En mi opinión, la mayoría de estas palabras que he comentado (y muchas de las que no he dicho nada) de la librería de Dutra, si las vas a tener en tu diccionario de uso general (como va a ser mi caso) valen la pena, ya que puestos a tenerlas, pues mejor usar sus versiones en código máquina.

Para mí modo de ver, un amento del 1.5x para arriba justifica el uso de la versión en código máquina aunque se aumente el tamaño de la palabra. Otros pensarán que lo mínimo sería un 2x o 3x o vete a saber, pero lo que creo que no admite discusión son los cuatro casos con multiplicadores formidables:

2/ 44x
FILL 42X
CMOVE 72X
CMOVE> 83X

Son un "must".

Aunque en general todos los aumentos de velocidad son un "sin sentido" si trabajas con un emulador puesto que puedes tener un 10x simplemente aumentando la velocidad de la simulación, pero incluso así, estas 4 "magníficas" vale la pena tenerlas en versión código máquina.

Aprovecho la ocasión para comentar que las palabras 2* y 2/ solo son válidas para número enteros con signo
e.g.

Código: Seleccionar todo

-5 2* -> -10
Pero estos mismos números considerados sin signo son:
65531 2* -> 65526

-32000 2/ -> -16000
que sin signo es:
33536 2/ -> 49536

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 14 Oct 2022 23:37

Buscando por internet he encontrado este video sobre un sencillo generador de laberintos para el Ace Forth.

Es tan simple como imprimir aleatoriamente una línea inclinada a la izquierda o a la derecha secuencialmente en cada posición de la pantalla. Se genera un laberinto simple (sin bifurcaciones).

Pero lo que más me ha llamado la atención es el comentario que hizo John Kennedy hace 7 meses:

Did you spot that error in that RAND word definition? The SWAP does nothing - in fact, it breaks how the word should work. It's been wrong in the manual for 40 yeas ;-)
···
···
The "modern" versions of the manual are simple reprints, so also wrong. Just remove SWAP.


O sea, que la definición de la palabra RAND del manual de JA tiene un BUG.

Ciertamente, si la miramos bien, vemos que es cierto:


: rand
?dup 0=
if
15403 @ swap
then
seed !
;


RAND es la palabra del generador de número aleatorios (manual páginas 82-83) que se encarga de cargar la semilla. Toma un número de la pila y, si es distinto de cero, lo guarda en la variable SEED. Pero si es cero, toma como semilla el valor que hay en la dirección 15403. Esta dirección corresponde a la variable del sistema que almacena el número de "frames" que han acontecido desde que se inició el JA (exactamente es el Low Word de un contador de 32 bits).

Pero en realidad RAND así definido no hace lo que se pretende. Debido al SWAP, lo que guarda en la semilla es el valor de la pila que había justo antes de colocar el 15403 en la misma. Si la pila estaba vacía antes de ejecutar n RAND, lo que está metiendo en SEED es el número que había justo debajo de la pila. Afortunadamente, este número puede ser cualquier cosa, por lo que no deja de ser una semilla también, pero NO la que se pretendía. De ahí que el error ha pasado desapercibido tanto tiempo, pues RAND funciona a pesar del bug.

Ya puestos pongo aquí el .TZX del programa MAZE.

maze.TZX
(374 Bytes) Descargado 7 veces


EDIT (15-10-2022): He puesto la versión corregida de MAZE.TZX. La que había puesto no tenía la redefinición de los caracteres con las líneas inclinadas a derecha e izquierda.

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 22 Oct 2022 15:14

Trabajando en el programa DELREPES que, de llegar a buen término, borrará automáticamente todas las palabras repetidas del diccionario he tenido que pensar un poco más sobre las palabras compilantes de usario, las definidas con COMPILER.

En principio nunca deberíamos encontrarnos con una palabra en cuya definición esté compilado el CFA de una palabra compilante de usuario. Esto es así porque las palabras compilantes son INMEDIATAS y durante la definición de una palabra se ejecutan y compilan otra CA diferente del CFA de la misma como explico en el hilo sobre las estructura de las palabras.

Pero además de inmediatas, solo se pueden ejecutar (su parte compilante, la asociada al CFA) estando en modo compilación.

Esto viene a cuento porque en el programa DELREPES que he comentado, si hay una palabra compilante de usuario repetida, necesito localizar, por razones que no vienen al caso, todas las palabras que haya en el diccionario que tengan compilado su CFA. En principio parecería que me puedo ahorrar este paso, pues como acabo de decir, NUNCA se compilan como tales.

Pero existe la posibilidad de compilar su CFA en el PF de una palabra. Se puede hacer de muchas maneras. Veamos un ejemplo:

Supongamos que hemos definido la palabra ABORT" (*) que es una compilante que durante la compilación toma del búfer de entrada una serie de caracteres terminada en comillas. Compila este mensaje y cuando se ejecuta la palabra, toma un flag de la pila. Si es 1 imprime el mensaje y aborta. Si es 0 no hace nada.

Si hago:

: TEST [ FIND ABORT" , ]  ;

NOTA: Otra manera equivalente de hacerlo es:

: TEST [COMPILE] ABORT"

usando la palabra [COMPILE] del estándar-79 que ya comenté aquí en su día. Esta palabra compila directamente el CFA de la que le sigue.

Si listamos TEST:

: TEST ABORT" ;

Si ejecutamos TEST se produce ERROR 4 (Compile Only Word)

Y esto es así por lo dicho anteriormente (remarcado en amarillo).

Por lo que parece que no tiene ningún sentido compilar el CFA de una compilante en la definición de una palabra, por lo que no haría falta buscarlas en caso de repetición.

Pero lo cierto es que sí se puede. ES un método un tanto retorcido, pero factible. Veámoslo:

Recordemos que la variable del sistema (1 byte) almacenada en la dirección 15422 contiene un byte de estado. El bit número 6 (el del 64) nos indica si estamos en modo compilación (1) o ejecución (0).

Si hemos compilado el CFA de ABORT" cuando se ejecute nos dará ERROR 4 pues estamos en modo ejecución. Para evitarlo hemos de poner el Ace Forth en modo compilación. Para ello ponemos a 1 el bit 6 de la variable anterior:

: TEST
15422 DUP C@ 64 OR SWAP C! \ Ponemos a 1 bit 6 del byte en (15422) sin alterar los demás.
[COMPILE] ABORT" \ Compilamos el CFA de ABORT"
15422 DUP C@ 191 AND SWAP C! \ Ponemos a 0 el bit 6 de antes-> volvemos a modo ejecución (**)
;


Si ejecutamos TEST así:

TEST no me lo digas"

Se ejecutaría sin dar error.

Si miráramos la memoria del diccionario, veríamos que se han compilado justo después del CFA del ; del final de TEST el CA de ABORT" (el CFA del Runtime), el tamaño de la frase (como entero sencillo de 2 bytes) y luego la frase "no me lo digas".

Esto es justamente lo que hace ABORT" cuando se compila dentro de una definición. Solo que aquí lo hace fuera de la palabra que lo contiene, justo a continuación del final del diccionario.

Vemos que todo funciona correctamente. Aunque este caso concreto parezca una tontería hacer algo así, lo que importa es que se puede hacer y posiblemente con otra compilante puede ser de utilidad. Vete a saber

La conclusión es que NO podemos descartar que alguien pueda usar palabras compilantes compiladas directamente (su CFA compilante) en la definición de una palabra, por lo que no me queda otra que aceptar esta posibilidad.

(*) Aunque no es relevante, pongo aquí su definición:
-1 COMPILER ABORT"
34 WORD C@ DUP ,
PAD + 1+ PAD 1+
DO
I C@ C,
LOOP
RUNS>
SWAP
IF
DUP 2+ SWAP @ TYPE
ABORT
ELSE
DROP
THEN
;

(**) Si no hacemos esto, al terminarse la palabra al ejecutarse el CA de ";" (punto y coma) seguiríamos en modo compilación y todo lo que escribiéramos a continuación se compilaría y al terminar con otro ";" daría ERROR 5 (Structure imbalance).

NOTA: Si listamos la palabra TEST:

: TEST
15422 DUP C@ 64 OR
SWAP C! ABORT" 15422 DUP
C@ 191 AND SWAP C!
;

Vemos que muestra ABORT" igual que si lo hubiéramos utilizado normalmente. Me explico:

La definiciones:

: TEST1
[COMPILE] ABORT"
;

y

: TEST1
ABORT" dime algo"
;

al listarse se muestra igual:

: TEST1
ABORT"
;

y

: TEST2
ABORT"
;


Pero ojo, NO son iguales. Si miramos en memoria veremos que en TEST1 está compilado el CFA de ABORT" mientras que en TEST2 está compilado el CA del Runtime deABORT" seguido del tamaño de la frase y luego la frase carácter a carácter.

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 24 Oct 2022 13:07

Modificación de palabras por programa.

Si queremos modificar el PF de una palabra durante la ejecución de una palabra, la clave es determinar la dirección de memoria del punto donde se ha de realizar la modificación.

En la palabras que almacenan datos, tales como las tipo VARIABLE, CREATE o una definida con una Definidora de Usuario es de lo más normal hacerlo. Tanto es así que todas ellas cuando se ejecutan lo primero que hacen es colocar en la pila su PFA (la dirección del primer byte de su Parameter Field) para que podemos acceder y/o modificar los datos allí contenidos.

Pero me refiero a modificar el Código de una palabra, por ejemplo, el código contenido en el PF de una palabra tipo COLON.

No voy a entrar en la utilidad y/o conveniencia de este tipo de actuaciones. Solo en cómo hacerlo.

Si por ejemplo queremos que durante la ejecución de una palabra se cambie el código de otra, e.g.:

Tenemos la palabra OPERACION que toma un número de la pila y nos lo duplica.

Ahora queremos que la palabra CAMBIADORA ejecute OPERACION y acto seguido nos la modifica de modo que en lugar de doblar un número nos lo eleve al cuadrado (multiplicarlo por sí mismo). Así, la próxima vez que se ejecute OPERACION elevará al cuadrado en vez de duplicar.

O sea, queremos que la palabra OPERACION

: OPERACION ( n - 2*n)
DUP +
;

se cambie a

: OPERACION ( n - n*n)
DUP *
;

durante la ejecución de CAMBIADORA.

Para ello CAMBIADORA necesita saber la dirección de memoria dónde está el + dentro de OPERACION para cambiarlo por un *.

Para ello definimos la palabra ADR así

: ADR ( - adr)
R> DUP >R
;

: CAMBIADORA ( n - 2*n>>n*n)
ADR SWAP OPERACION
<cambio de + por *>
;

El paréntesis ( n - 2*n>>nn) "intenta" indicar que la primera vez duplica y sucesivas ejecuciones elevarían al cuadrado

Ejecutamos CAMBIADORA con el número "n" en la pila:

Al ejecutarse ADR en CAMBIADORA el Forth apunta en la pila de retornos (Rstack) la dirección de la siguiente CA (el SWAP) para saber donde continuar tras terminar con ADR. Esta dirección es la que ADR nos coloca en la pila (la llamamos "adr")

Tras ejecutarse OPERACION tenemos en la pila 2*n y justo debajo "adr". Sabemos que "adr" es la dirección del CA del primer SWAP. Así que la dirección del CA de OPERACION es "adr"+2. Allí está almacenado el CFA de OPERACION. Luego sabemos que la dirección del CA de + en el PF de OPERACION se calcula así:

(adr+2)+4 que en Forth es adr 2+ @ 4 +

En esa dirección hemos de colocar el CFA de * por lo que teniendo "adr" en la segunda posición de la pila (justo debajo de 2*n) hemos de hacer:

<cambio de + por *>=SWAP 2+ @ 4 + [ FIND * ] LITERAL SWAP !

La definición de CAMBIADORA nos queda así:

: CAMBIADORA ( n - 2*n>>n*n)
ADR SWAP OPERACION
SWAP 2+ @ 4 + [ FIND * ] LITERAL SWAP !
;

Si hacemos:

3 OPERACION . nos imprime 6
3 CAMBIADORA . nos imprime 6
3 OPERACION . nos imprime 9
3 CAMBIADORA . nos imprime 9
etc.

Si listamos OPERACION vemos:

: OPERACION 
DUP *
;

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 02 Nov 2022 12:55

De vuelta con los SIN (III)

Dutra ha actualizado recientemente su ebook (pág. 54/104) y ahora ya pone la codificación de SIN con la aproximación de Bhaskara.

La ha llamado cSIN y está codificada con números enteros y el resultado es un entero entre 0 y 255 para los valores de 0 a 1 del seno. Solo admite valores enteros entre 0 y 90 grados.

Es realmente rápida (~200 sin/segundo). Para lo pequeña que es (59 bytes o 84 si incluimos RSHIFT) es un buen substituto de la tabla de senos (aunque mucho menos precisa).

NOTA: Tras probarla me parece que se ha olvidado de poner un SWAP justo antes del /MOD.

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 06 Nov 2022 23:47

Me acabo de dar cuenta que al ejecutar VLIST deja un 0 en la pila al terminar.

En el manual no he visto nada al respecto. Quizás es un bug.

Todo este tiempo y no me había enterado hasta hoy...

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 09 Nov 2022 00:10

Versión de DUP en código máquina lo más rápida que he sabido hacerla.

No cabe en la ROM, por lo que se ha de usar como palabra de usuario.

Esta versión, si bien es casi el doble de rápida que la original del Ace Forth, tiene un PF casi 4x mayor.

Velocidades medidas en modo FAST

DUP size speed speed
(bytes) (ej./s) (relativa)
---------------------------------
Original 5 8.658 1x
Nuevo 18 16.529 1.9x

Código: Seleccionar todo

; DUP
        LD      HL,($3C3B)    ; SPARE=$3C3B -> Contiene la "address" del byte justo después del final de la  pila.
        DEC     HL
        DEC     HL
        LD      E,(HL)
        INC     HL
        LD      D,(HL)
        INC     HL
        LD      (HL),E
        INC     HL
        LD      (HL),D
        INC     HL
        LD      ($3C3B),HL
        JP      (IY)


dup.TZX
(115 Bytes) Descargado 3 veces

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 09 Nov 2022 16:12

Versión de SWAP en código máquina lo más rápida que he sabido hacerla.

No cabe en la ROM, por lo que se ha de usar como palabra de usuario.

Esta versión es el doble de rápida que la original del Ace Forth, pero también tiene un PF algo más del doble de grande.

Velocidades medidas en modo FAST

SWAP size speed speed
(bytes) (ej./s) (relativa)
---------------------------------
Original 10 6.897 1x
Nuevo 22 14.493 2.1x


Código: Seleccionar todo

;  SWAP
        LD      HL,($3C3B)      ; SPARE Address of byte past data stack's end.
        DEC     HL
        LD      D,(HL)
        DEC     HL
        LD      E,(HL)
        DEC     HL
        LD      B,(HL)
        LD      (HL),D
        DEC     HL
        LD      C,(HL)
        LD      (HL),E
        LD      HL,($3C3B)      ; SPARE
        DEC     HL
        LD      (HL),B
        DEC     HL
        LD      (HL),C
        JP      (IY)            ; Back to Forth.


swap.TZX
(120 Bytes) Descargado 3 veces

Elurdio
Mensajes: 598
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 144 veces
Agradecimiento recibido: 110 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 09 Nov 2022 16:55

Versión de ROT en código máquina lo más rápida que he sabido hacerla.

No cabe en la ROM, por lo que se ha de usar como palabra de usuario.

Esta versión es 6 veces más rápida que la original del Ace Forth (y el doble que la de la ROM modificada), pero también tiene un PF casi el triple de grande.

Velocidades medidas en modo FAST

ROT size speed speed
(bytes) (ej./s) (relativa)
---------------------------------
Original 10 2.066 1x
Modific. 10 6.135 3x --> Cabe en ROM
Nuevo 28 12.195 5.9x


Código: Seleccionar todo

; ROT
        LD      HL,($3C3B)      ; SPARE Address of byte past data stack's end.
        DEC     HL
        LD      D,(HL)
        DEC     HL
        LD      E,(HL)
        DEC     HL
        LD      B,(HL)
        LD      (HL),D
        DEC     HL
        LD      C,(HL)
        LD      (HL),E       
        DEC     HL
        LD      D,(HL)
        LD      (HL),B
        DEC     HL
        LD      E,(HL)
        LD      (HL),C
        LD      HL,($3C3B)      ; SPARE
        DEC     HL
        LD      (HL),D
        DEC     HL
        LD      (HL),E
        JP      (IY)            ; Back to Forth.


Recordar que ROT original está programado internamente en Ace Forth, no en código máquina. De ahí que sea relativamente lento.

rot.TZX
(125 Bytes) Descargado 4 veces


Volver a “Jupiter Ace”

¿Quién está conectado?

Usuarios navegando por este Foro: amakuja y 1 invitado