Jupiter Ace Forth: Utilidades II

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 13 Sep 2022 16:30

He abierto este nuevo hilo como continuación del anterior Jupiter Ace Forth: Utilidades.

Lo hago porque el hilo original, cuando comienza una nueva página, pone como "último post de la página anterior" uno que no es (la última vez ni siquiera era el anterior sino dos antes del último).

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 13 Sep 2022 16:31

Otra palabra:

2* ( n - 2*n )

e.g. 8 2* devuelve 16

No es del estándar FORTH-79

Su definición, a bote pronto, sería:

: 2*
2 *
;

pero en el manual se define asÍ:

: 2*
DUP +
;

La primera versión es más lenta debido a la la multiplicación. La segunda versión es "más Forth" y 2.5x más rápida que la primera.

La versión en código máquina de la librería de Dutra es una versión clásica que ya se encuentra por ejemplo aquí (mayo-1993).

Se basa en el hecho que cada vez que añadimos un 0 por la derecha a un número es lo mismo que multiplicarlo por la base.

Así en base 2 (binario), un shift o desplazamiento a la izquierda de todos los bits introduciendo un cero por la derecha equivale a multiplicar por dos.

De modo similar a lo que hacemos nosotros en base diez, al añadir un cero por la derecha a un número, lo multiplicamos por diez.

Números en el fuente en Hexadecimal salvo que se especifique la base.

Código: Seleccionar todo

; 2* ( n - 2*n)
; -----------------
            RST     18      ; DE=TOS

            SLA     E       ; Shift a la izquierda de un bit de DE -> DE=2*DE
            RL      D

            RST     10      ; TOS=DE
            JP      (IY)    ; Vuelta a Forth


La versión en código máquina es 3.3x más rápida que la versión Forth con DUP y 8.3x más rápida que la primera.

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 13 Sep 2022 21:47

Otra palabra:

2/ ( n - n/2 )

e.g. 8 2/ devuelve 4

No es del estándar FORTH-79

Su definición Forth:

: 2/
2 /
;

Es bastante lenta por la división (mucho más lenta que la multiplicación).

La versión en código máquina de la librería de Dutra es una versión clásica que ya vemos en la misma cita del post anterior.

De modo similar al caso de la multiplicación, se basa en que cada vez que quitamos el dígito de la derecha de un número equivale al cociente que se obtiene al dividir por la base.

Números en el fuente en Hexadecimal salvo que se especifique la base.

Código: Seleccionar todo

; 2/ ( n - n/2)
; -----------------
            RST     18      ; DE=TOS

            SRA     D       ; Shift a la derecha de un bit de DE -> DE=DE/2
            RR      E

            RST     10      ; TOS=DE
            JP      (IY)    ; Vuelta a Forth

La versión en código máquina es 44x más rápida que la versión Forth.

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 13 Sep 2022 22:45

Otra palabra:

2DUP ( n1,n2 - n1,n2,n1,n2 )

e.g. SP! 2 5 2DUP .S se muestra el contenido de la pila: { 2 5 2 5 }

Forma parte de la Extensión de Números Dobles del estándar FORTH-79

En el manual se define así:

: 2DUP
OVER OVER
;


Recordar que SP! borra toda la pila y .S nos muestra en pantalla el contenido de la pila sin alterarla.

  • En Ace Forth la pila es software (no es la pila hardware del Z80). Empieza 12 bytes después del fin del diccionario y aumenta en memoria al crecer.
  • HERE nos pone en la pila la dirección de primer bytes justo después del diccionario.
  • Este valor que devuelve HERE está almacenado en la variable del sistema STKBOT $3C37 (15415).
  • La dirección de memoria del primer byte justo después de tope de la pila se almacena en la variable del sistema SPARE $3C3B (15429).
  • Cada posición de la pila ocupa dos bytes (un entero sencillo).
Los número enteros dobles y los números en coma flotante tienen 4 bytes por lo que ocupan DOS posiciones de la pila.

e.g. SP! 12.3 .S me dice que la pila es: { 12288 16914 }. Vemos que el número en coma flotante 12.3 ocupa dos posiciones en la pila, cada una con un número entero sencillo.

2DUP es útil para duplicar tales números pero también se usa con enteros sencillos para duplicar los dos primeros en la pila.

La versión en código máquina de la librería de Dutra es la más rápida que conozco. Es más rápida que el DUP en código máquina de la ROM del Ace Forth que solo duplica un entero y no dos como 2DUP.

Lo que hace es copiar el bloque de memoria que contiene los dos enteros sencillos (el TOS y el anterior) y copiarlos justo encima. Modifica la variable SPARE en consecuencia.

Código: Seleccionar todo

; 2DUP ( n1,n2 - n1,n2,n1,n2)
; -----------------
            LD      HL,(3C3B)   ; HL=address[TOS]+2 (cada elemento de la pila ocupa 2 bytes)
                                ; (3C3B) contiene la dirección del byte justo después del final de la pila.
                                ; Es la variable del sistema SPARE.

            LD      D,H         ; DE=address[TOS]+2=(SPARE)
            LD      E,L

                ; Copiamos los dos últimos números de la pila (4 bytes en total) por encima de ellos en la pila.

            LD      BC,FFFC     ; BC=-4 (decimal)
            ADD     HL,BC       ; HL=address[TOS]-2
            LD      BC,0004
            LDIR

            EX      DE,HL       ; Actualizamos SPARE a su nuevo valor.
            LD      (3C3B),HL
            JP      (IY)        ; Vuelta a Forth


La versión en código máquina es 3.1x veces más rápida que la versión Forth.

NOTA(1): Dutra indica un 12.3x. Este 12.3x es el que se obtiene con al ROM original del Ace Forth pero hay que tener en cuenta que yo uso la ROM modificada con una versión de OVER mucho más rápida que la versión que viene en el Ace Forth original. Este OVER es una modificación del OVERTurtle. Ya he hablado de ella en otro post de este hilo (bueno, del hilo anterior a éste).

NOTA(2): Los comentarios son un poco enrevesados con los TOS y el address[TOS], etc. Voy a intentar aclararlo un poco:

Cada número entero sencillo ocupa una posición de la pila. Cada posición de la pila son 2 bytes: Primero va el Low byte del número y luego, más arriba en memoria, el High byte del mismo.

address[TOS] es la dirección de memoria donde empieza la posición del Tope de la pila o TOS. Es la que contiene el Low byte.
address[TOS]+1 es la dirección de memoria del High byte del número del Tope de la pila o TOS.
address[TOS]+2 es la dirección de memoria del primer byte justo después del final de la pila = el valor almacenado en SPARE.

De todo lo anterior, vemos que la dirección de memoria del Low byte del número justo debajo del Tope de la pila (el segundo número de la pila) es:

(SPARE)-4 =address[TOS]+2-4=address[TOS]-2

Quizás hubiera quedado más claro diciendo simplemente que copiamos los 4 bytes de arriba de la pila (los dos enteros sencillos de arriba de la pila). La dirección destino nos la da SPARE y el primer byte a copiar es (SPARE)-4.

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 14 Sep 2022 16:46

Otra palabra:Otra palabra:

FILL ( adr,len,n - )

Rellena con "n" todos los bytes de memoria empezando en la dirección "adr" hasta "adr+len-1" (en total "len" bytes).

Está en el estándar FORTH-79

Se puede definir en Ace Forth de la siguiente manera:

: FILL ( adr,len,n - ) (*)
>R OVER + SWAP
DO
J I C!
LOOP
R> DROP
;

Los números están en Hexadecimal.

Código: Seleccionar todo

; FILL ( adr,len,n - ) Rellenamos desde la dirección "adr" tantos "len" bytes con el byte "n"
; -----------------
            RST     18          ; DE=TOS=n
            LD      A,E         ; A=n
            RST     18          ; DE=TOS=len
           
            LD      B,D         ; BC=len
            LD      C,E
           
            RST     18          ; DE=adr
            EX      DE,HL       ; HL=adr
            LD      E,A         ; E=n

            ; En este punto tenemos BC=número bytes a rellenar
            ;                       HL=primera posición de memoria a rellenar
            ;                       E =byte de relleno
               
LOOP:       LD      A,C         ; Jump FORTH si len=0 Terminamos si hemos copiado los "len" bytes.
            OR      B
            JR      Z,FORTH

            LD      (HL),E      ; Copiamos el byte de relleno en memoria y actualizamos BC y HL
            INC     HL
            DEC     BC
            JR      LOOP
FORTH:      JP      (IY)

Esta versión es 21x más rápida que la versión en Forth con DO/LOOP.

Hay otras versiones más rápidas que la de Dutra. Pongo por ejemplo ésta con LDIR:

Código: Seleccionar todo

; FILL ( adr,len,n - )

        .LIST
   
        RST     18H         ; A=n
        LD      A,E
       
        RST     18H         ; BC=len
        LD      B,D
        LD      C,E
       
        RST     18H         ; HL=adr
        EX      DE,HL
       
        LD      E,A         ; Si len=0 salta a FIN y termina.
        LD      A,B         ; E=n
        OR      C
        JR      Z,FIN
       
        LD      (HL),E      ; (adr)=n Rellena con "n" la primera posición a rellenar
        DEC     BC          ; BC=len-1 pues ya hemos rellenado un posición.
       
        LD      A,B         ; Si len=1 salta a FIN y termina.
        OR      C
        JR      Z,FIN
       
        LD      E,L         ; DE=segunda posición a rellenar=adr+1
        LD      D,H
        INC     DE
       
        LDIR                ; Al hacer LDIR con HL=adr, DE=adr+1 y BC=len-1 y habiendo puesto "n" en la primera
                            ; posición, el efecto es equivalente a un FILL:
                            ; Copia (adr)   a (adr+1). Como (adr)=n   -> (adr+1)=n
                            ; Copia (adr+1) a (adr+2). Como (adr+1)=n -> (adr+2)=n
                            ; ···
                            ; y así hasta haber rellenado todo el bloque.

FIN:    JP      (IY)
   
        .END

Esta versión con LDIR es 2x la de Dutra y 42x la versión en Forth con DO/LOOP. Aunque también es 6 bytes más extensa que la de Dutra (30 vs 36) el incremento del 100% en velocidad lo compensa.

Aunque parezca que la versión Forth es demasiado lenta, en realidad es suficientemente rápida para muchos usos pues alcanza los 3000_bytes/segundo.

EDIT (15-9-2022): NO me había fijado que Dutra afirma: "19.7x the Secondary" lo que parece indicar una versión Forth más rápida que la que he puesto yo. De momento no se me ocurre otra mejor... a no ser que usemos words en vez de bytes o sea usar ! en vez de C! lo que requiere una pequeña preparación previa pero seguro que compensa. Luego lo miro de hacer y lo compruebo. De momento, el +LOOP es la mitad de rápido que el LOOP pero usaremos la mitad. Ademas el C! es algo más rápido que el ! pero usaremos la mitad de ! que de C!... todo apunta a que será más rápido que el que puse.

(*) OJO: FILL definido así solo funciona con bloques de memoria en los los índices del DO tengan el mismo signo considerados como enteros con signo. Esto es así porque en Ace Forth DO/LOOP interpreta el índice y el límite como números CON signo.

e.g. 40000 100 n FILL funciona porque tanto 40000 como 40100 (inicio y fin del DO) son ambos negativos (como enteros con signo): -25536 y -25436 respectivamente.

En cambio: 32000 1000 n FILL no funciona porque 32000 es positivo pero 33000 es negativo (como enteros con signo)..

Esto se puede evitar o bien usando BEGIN/UNTIL en lugar del DO/LOOP o bien dividiendo el rellenado en dos FILLs uno en la zona positiva y el otro en la zona negativa y el byte entre medio de las dos zonas:

e.g. 32000 1000 n FILL se haría:
32000 767 n FILL
n 32767 C!
32768 232 n FILL

NOTA: Las pruebas de velocidad están hechas para un rellenado de 100 bytes. Si fuera para muy pocos bytes el peso de los pasos previos al rellenado en sí aumenta por lo podrían variar las diferencias de velocidad entre versiones.

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 14 Sep 2022 22:01

No hace mucho hablaba en este post de un nuevo tipo de palabras primitivas que se me "ocurrió" crear y que nunca antes las había visto.

Pues bien, acabo de darme cuenta que Dutra creó al menos dos en su librería. Así que no inventé nada :( .

La primera la descubrí por casualidad hoy mismo. Tengo el diccionario de "Filling the Gaps" cargado y guardado en un .TZX. Contiene las palabras que estoy comentando en estos últimos post. Una de las que contiene es R@.

Lo he cargado y se me ha ocurrido hacer un listado con mi listador especial "WLIST" y al llegar a R@ ha escupido bastante basura en pantalla. Luego ha seguido normalmente con el resto de palabras. Una de las posibles razones que suceda esto con WLIST es que la palabra que lo produce sea de un tipo "desconocido" que WLIST no entiende.

Mirándo su definición he visto que este era el caso: R@ es un "nuevo" tipo de palabra. Concretamente del que yo, en mi ignorancia, creí haber inventado.
Elurdio escribió:···
Lo que se me ha ocurrido es crear palabras que nos permitan usar estas "palabras internas" como una palabra más del Ace Forth. Lo que hago es crear un nuevo tipo de palabra. Las llamaré tipo "Internas".

Vienen a ser un tercer caso de primitivas: Las normales en las que (CFA) = PFA. Las "raras" de BoldField en las que (CFA) apunta dentro del PF pero no precisamente al inicio y ésta en las que (CFA) apunta a la ROM y no tienen PF.

Es más, revisando más a fondo el ebook, he encontrado que la primera palabra que hice de "mi nuevo tipo" ya la había hecho Dutra años atrás...
Elurdio escribió:En el caso del stk-zero voy a crear la palabra CERO así:

CREATE CERO

Ahora solo hemos de colocar en su CF el contenido del CF de stk-zero que vale $068A (1674)

1674 CERO 2- !

Y ya tenemos nuestra palabra CERO de tipo "Interno" y sin PF (Parameter Field).

CERO no es ni listable ni editable.

Pongo dos extractos del ebook de Dutra con las palabras en cuestión:
rfetch_dutra.png
rfetch_dutra.png (5.29 KiB) Visto 940 veces

zero_dutra.png
zero_dutra.png (8.77 KiB) Visto 940 veces

Como se dice por ahí: Mi gozo en un pozo y también Ya está todo inventado

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 15 Sep 2022 09:31

Me he dado de bruces con el límite de mi cronómetro TIMEX_INI/TIMEX. Aunque era algo que ya sabía, como nunca se me presentó el caso lo había olvidado... Ayer me volví un poco loco por unos resultados sin sentido cronometrando una palabra hasta que me acordé del susodicho límite que me parece que nunca lo he comentado explícitamente:

TIMEX puede medir como máximo tiempos de 1308 segundos (21.81 minutos)

Antes era impensable que se alcanzara, pero desde que uso el 32x (*) cuando cronometro palabras es posible y ayer sucedió...

NOTA: La variable del sistema del Ace Forth FRAMES (3C2B) es un contador de frames de cuatro bytes. TIMEX_INI pone a cero los cuatro, pero TIMEX solo leo los 2 bytes menos significativos dado que el límite de 21 minutos me parecía más que suficiente y operar con enteros sencillos es más rápido y fácil que con enteros dobles.

(*) Aunque lo pongo a 32x me parece que no pasa de 10x o así. Tengo pendiente comprobar cual es el multiplicador efectivo real.

EDIT: En el EightyOne poniéndolo a 32x solo alcanza 10x (Frame Skip Enabled) o 8.7x (Frame Skip Disabled)
NOTA: Depende del PC. En mi caso lo dicho arriba es para el Desktop de Casa mientras en el portátil del trabajo son 11x y 10x respectivamente.

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 15 Sep 2022 12:20

Otra palabra:

CMOVE ( adr1,adr2,len - )

Copiamos el bloque de memoria que empieza en "adr1" y de longitud "len" a la posición "adr2" empezando la copia por el inicio del bloque.
Para evitar sobreescribir zonas solapadas debe usarse cuando "adr1">"adr2".

Está en el estándar FORTH-79

En Ace Forth podría definirse así usando BEGIN/UNTIL:

Código: Seleccionar todo

: CMOVE ( adr1,adr2,len - )
 ?DUP 0=
 IF
  DROP DROP EXIT
 THEN
 >R
 BEGIN
  OVER C@ OVER C! 1+
  SWAP 1+ SWAP R> 1-
  DUP >R 0=
 UNTIL
 R> DROP DROP DROP
;


NOTA: Todos los cronos son moviendo un bloque de 100 bytes

Definido así solo alcanza a mover ~730 bytes/segundo y eso que tengo la ROM modificada con el OVER rápido.

Otra posible definición más rápida a costa de usar DO/LOOP y su consabida limitación ya comentada en FILL:

Código: Seleccionar todo

: CMOVE ( adr1,adr2,len - )
 ?DUP 0=
 IF
  DROP DROP EXIT
 THEN
 ROT SWAP OVER + SWAP
 DO
  I C@ OVER C! 1+
 LOOP
 DROP
;


Definido así alcanza a mover ~1740 bytes/segundo (2.4x)

Código: Seleccionar todo

; CMOVE ( adr1,adr2,len - )     Copiamos un bloque que empieza en "adr1" y longitud "len" a un bloque
;                               que empieza en adr2. Se usa cuando adr1>adr2
;                               La copia es: adr1->adr2, adr1+1->adr2+1, .... , adr1+len-1 -> adr2+len-1.
; -----------------
            RST  18     ; BC=len
            LD   B,D
            LD   C,E
           
            RST  18     ; DE=adr2
            PUSH DE
           
            RST  18     ; HL=adr1
            EX   DE,HL  ; DE=adr2
            POP  DE
           
            LD   A,B    ; if "len"=0 volvemos a Forth (FIN)
            OR   C
            JR   Z,FIN
           
            LDIR        ; Realizamos la copia ascendente empezando por los inicios de bloques.
                        ; HL=adr1, DE=adr2, BC=len
           
FIN:        JP   (IY)


Esta versión Dutra es 172x más rápido que la versión Forth con BEGIN/LOOP o 72x que la versión DO/LOOP

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 15 Sep 2022 16:41

En el post sobre la palabra FILL añadía esta mañana estO:

Elurdio escribió:···
EDIT (15-9-2022): NO me había fijado que Dutra afirma: "19.7x the Secondary" lo que parece indicar una versión Forth más rápida que la que he puesto yo. De momento no se me ocurre otra mejor... a no ser que usemos words en vez de bytes o sea usar ! en vez de C! lo que requiere una pequeña preparación previa pero seguro que compensa. Luego lo miro de hacer y lo compruebo. De momento, el +LOOP es la mitad de rápido que el LOOP pero usaremos la mitad. Ademas el C! es algo más rápido que el ! pero usaremos la mitad de ! que de C!... todo apunta a que será más rápido que el que puse.
···


Acabo de hacer una versión que usa words en vez de bytes. Tiene bastante preparación, pues tiene que:

  • Convertir el byte de relleno en un word duplicado: e.g. sea byte=3E -> word=3E3E. Lo he hecho con una multiplicación pues hacerlo copiando memoria es solo ligeramente más rápido y como solo se hace una vez, pues ya me vale.
  • Separar la longitud "len" en 1 y n-1 si es impar.
  • Si es impar rellenar el primer byte y aumentar en 1 la dirección de inicio
  • Si len=0 terminar
  • Hacer el relleno de words con DO/+LOOP.

Código: Seleccionar todo

: FILL (adr,len,n)
 DUP 256 * + >R         ; Byte de relleno -> word de relleno (duplico el byte) y lo pongo en Rstack. e.g. 3E -> 3E3E
 
 2 /MOD 2* >R           ; Paso a Rstack la parte par de la longitud "len"
 
 IF                     ; Si "len" era impar -> relleno el primer byte y hago adr=adr+1
  I' OVER C! 1+
 THEN
 
 I 0=                   ; Si "len" = 0 teminamos.
 IF
  R> R> DROP DROP DROP
  EXIT
 THEN
 
 DUP R> + SWAP          ; Recupero "len" del Rstack y calculo: Límite de bucle -> adr+len Índice del bucle -> adr
 DO
  J I ! 2               ; Vamos rellenando. El bucle tiene incremento 2 (word)
 +LOOP
 R> DROP
;


Esta versión es ~15% más rápida que la que puse rellenando de byte en byte. Pero también ocupa más (aunque en aquella me olvidé de poner el control de len=0). No sé si compensa.

Ahora la versión de Dutra es 18x ésta en vez de los 21x de antes. Es incluso algo más rápida que la secundaria "desconocida" (Dutra 19.7x la desconocida)

NOTA: Se podría hacer algo parecido con los CMOVEs y demás (usar words en vez de bytes) y se obtendría un aumento de velocidad por el estilo de éste. Siendo tan poco, no me molesto en hacerlo.

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 15 Sep 2022 23:20

Otro tema interesante del ebook de Dutra que me olvidé de comentar es lo referente a las palabras relacionadas con la Pila de Retornos (aka Rstack): >R R> I J e I'

Como ya se dijo, el Ace Forth implementa el Rstack en la pila hardware del Z80. Una de las ventajas que comporta este enfoque es que las palabras relacionadas con el Rstack se ejecutan más rápido que en las implementaciones en las que el Rstak está implementado por Software.

Puesto que el Rstack es hardware, al Ace Forth no le queda otra que implementar por software la pila de Datos (aka Stack a secas o pila a secas).
Veamos unas consecuecias:

Supongamos que tenemos tenemos dos o más números en la pila y queremos sumar uno al segundo. Lo normal es hacerlo así:

SWAP 1+ SWAP

Pero también podemos implementarlo así:

>R 1+ R>

Pues bien, el segundo método se ejecuta un 66% más rápido que el primero en el Ace Forth (1.66x)

Si tenemos que aplicar muchas veces DUP a un número que no cambia, por ejemplo dentro de un bucle, nos sale más a cuenta pasar dicho número al Rstack y cada vez que lo necesitamos lo traemos a la pila con I, J o I' según el caso. Podemos ver las velocidades con DUP como referencia:

DUP   1.0x
I 1.6x
J 1.6x
I' 1.5x

Así es más rápido dentro de un bucle DO/LOOP hacer: I I que no I DUP, etc.

La posibilidadess son muchas: Usar Rstack para tener variables/constantes locales, etc.

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 16 Sep 2022 11:29

Otra palabra:

CMOVE> ( adr1,adr2,len - )

Copiamos el bloque de memoria que empieza en "adr1" y de longitud "len" a la posición "adr2" empezando la copia por el final del bloque (adr1+len-1).
Para evitar sobreescribir zonas solapadas debe usarse cuando "adr1"<"adr2".

NO está en el estándar FORTH-79

Su definición en Forth en similar a la de CMOVE solo que ahora empezamos por el final del bloque y vamos decreciendo en memoria: (uso DO/+LOOP)

Código: Seleccionar todo

: CMOVE> ( adr1,adr2,len - )
 ?DUP 0=
 IF
  2DROP EXIT
 THEN
 >R I + 1- SWAP
 1- DUP I +
 DO
  I C@ OVER C! 1-
  -1
 +LOOP
 R> 2DROP
;


Definido así alcanza los ~1480 bytes/segundo. Es más lento que CMOVE (~1740) ya que DO/+LOOP es la mitad de rápido que DO/LOOP porque hay que poner el step en la pila y +LOOP tiene que tomarlo de la pila.

La versión en código máquina de la librería Dutra: (número en Hexadecimal)

Código: Seleccionar todo

; CMOVE> ( adr1,adr2,len - ) Copiamos un bloque que empieza en "adr1" y longitud "len" a un bloque
;                               que empieza en adr2.
;                               La copia es: adr1+len-1->adr2+len-1, adr1+len->adr2+len, .... , adr1->adr2
; -----------------
            RST     18      ; DE=len
           
            LD      A,D     ; Si len<>0 Continúa en CONT1. Si no, elimina adr1 y adr2 de la pila y vuelve a Forth.
            OR      E
            JR      NZ,CONT1
            RST     18
            RST     18
            JP      (IY)
           
CONT1:      DEC     DE      ; BC=len-1
            LD      B,D
            LD      C,E
           
            RST     18      ; HL=adr2+len-1
            EX      DE,HL
            ADD     HL,BC
            PUSH    HL
           
            RST     18      ; HL=adr1+len-1 (final bloque fuente)
            EX      DE,HL   ; DE=adr2+len-1 (final bloque copiado)
            ADD     HL,BC   ; BC=len
            POP     DE
            INC     BC

            LDDR            ; Realizamos la copia descendente empezando por los finales de bloques.
                            ; HL=adr1+len-1, DE=adr2+len-1, BC=len
                           
            JP      (IY)


La versión en código máquina es 83x más rápida que la versión Forth con DO/+LOOP.

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 16 Sep 2022 12:26

Otra palabra:

MOVE ( adr1,adr2,len - )

Es una combinación de CMOVE y CMOVE>. En función de si adr1 es mayor o menor que adr2 escoge el método de copia: como CMOVE o como CMOVE>.

No está en el estándar FORTH-79. Aunque allí sí hay una palabra con este nombre, no son la misma

En Forth sería algo así como:

MOVE ( adr1,adr2,len - )
OVER 4 PICK U<
IF
CMOVE
ELSE
CMOVE>
THEN
;

Aquí CMOVE y CMOVE> podrían tanto ser las versiones Forth como las versiones en código máquina.

La versión en la librería Dutra es (números en Hexadecimal)

Código: Seleccionar todo

; MOVE (adr1, adr2, len)   
; -----------------
            RST     18      ; BD=len
            LD      B,D
            LD      C,E
           
            LD      A,D     ; Si len<>0 salta a CONT1. Si es 0 termina.
            OR      E
            JR      NZ,CONT1           
            RST     18      ; Saca de la pila adr2 y adr1
            RST     18         
            JP      (IY)    ; Vuelta a Forth
                   
CONT1:      RST     18      ; DE=adr2
            PUSH    DE
           
            RST     18      ; DE=adr1
            POP     HL      ; HL=adr2
            PUSH    HL
           
            AND     A       ; Si HL<DE (adr2<adr1) continúa en CONT2 (empieza por el inicio del bloque)
            SBC     HL,DE
            POP     HL
            JR      C,CONT2
           
            ;   Esta sección corresponde a mover el bloque empezando por el final.
           
            DEC     BC      ; BC=len
            ADD     HL,BC   ; DE=adr2+len-1 (final bloque copiado)
            EX      DE,HL   ; HL=adr1+len-1 (final bloque fuente)
            ADD     HL,BC
            INC     BC
           
            LDDR            ; Realizamos la copia descendente empezando por los finales de bloques.
           
            JP      (IY)    ; Vuelta a Forth
           
            ;   Esta sección corresponde a mover el bloque empezando por el incio.
           
CONT2:      EX      DE,HL   ; Realizamos la copia ascendente empezando por los inicios de bloques.
            LDIR            ; HL=adr1, DE=adr2, BC=len

            JP      (IY)


La versión Dutra es ~1.7x la versión Forth con CMOVE y CMOVE> en código máquina.

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 16 Sep 2022 23:52

La librería Dutra sigue con otras muchas palabras. Las obvio hasta llegar a las Extensiones Dobles del FORTH-79

2DUP ya la hemos visto antes. Veremos ahora las restantes: 2DROP, 2OVER, 2SWAP, 2ROT (están en Extensión Dobles FORTH-79)
No habla ni de 2ROLL ni de 2PICK (No están en FORTH-79)

2DROP ( n1,n2 - )
Elimina las dos primeras posiciones de la pila o lo que es lo mismo, también elimina el primer entero doble o número en coma flotante de la pila.

En Forth sería:

: 2DROP ( n2,n1 - )
DROP DROP
;

La versión Dutra no es la más rápida pero sí la mas corta (16 bytes de los cuales: 12 la cabecera y tan solo 4 el Parameter Field PF). Es incluso más corta que la versión FORTH (18 bytes)

NOTA: Todos los números en hexadecimal

Código: Seleccionar todo

; 2DROP ( n2,n1 - )

   RST  18   ; DE=n1
   RST  18   ; DE=n2
   JP   (IY)   ; Vuelta a Forth

Esta versión es 2x más rápida que la versión Forth además de más corta que ésta última.

Que no es la más rápida lo podemos ver con esta otra versión:

Código: Seleccionar todo

; 2DROP ( n2,n1 - )

   LD   HL,$3C3B      ;  HL=SPARE, la variable que guarda la dirección del byte justo después del final de la pila.
   
   DEC   (HL)         ; (SPARE)=(SPARE)-4
   DEC   (HL)
   DEC   (HL)
   DEC   (HL)
   
   JP   (IY)         ; Vuelta a Forth

Esta versión reduce el tamaño de la pila en 4 bytes, lo que equivale a 2DROP. Es 1.85x la versión anterior y 3.7x la versión FORTH.

Por contra su tamaño es de 21 bytes (5 bytes más que la de Dutra)

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 17 Sep 2022 13:22

Otra palabra:

2SWAP (n4,n3, n2,n1 - n2,n1, n4,n3 )

Intercambia los dos primeros números dobles de la pila. Cada número doble son don número sencillos (d2,d1 - d1,d2) dónde "d" representa un número doble. Así, d1 es n2,n1 y d2 es n4,n3

En Forth:

: 2SWAP ( n4,n3, n2,n1 - n2,n1, n4,n3)
4 ROLL 4 ROLL
;

Librería Dutra: (números en hexadecimal)

Código: Seleccionar todo

; 2SWAP ( n4,n3, n2,n1 - n2,n1, n4,n3 )

;TOS = Top Of Stack = el primer número de la pila

            CALL    084E    ; BC=n1 Esta rutina es similar a RST 18 pero en vez de DE usa BC
            RST     18      ; DE=n2
            EXX
            RST     18      ; DE'=n3
            PUSH    DE
            RST     18      ; DE'=n4
            PUSH    DE     
            EXX
            RST     10      ; TOS=n2
           
            LD      D,B     ; DE=n1
            LD      E,C
           
            RST     10      ; TOS=n1
            EXX
            POP     DE      ; DE'=n4
            RST     10      ; TOS=n4
            POP     DE      ; DE'=n3
            RST     10      ; TOS=n3
            EXX
            JP      (IY)

Lo que hace es sacar de la pila con RST 18h los cuatro enteros y guardarlos en Registros del Z80, incluidos lo 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 es 2.3x más rápida que la versión Forth.

El código se podría acortar 2 bytes pues si nos fijamos bien los dos últimos EXX se podrían eliminar pues son innecesarios. En cuanto al aumento de velocidad que implica esta modificación es realmente "pírrico" (menos del 1%).

Veamos ahora otra versión mucho más rápida que la de Dutra y tan solo 3 bytes mayor:

Código: Seleccionar todo

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

; 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
; vamos a mover los últimos 8 bytes a la posición que ocupaban los 8 bytes originales.
; Con esto hemos hecho un 2SWAP y además el valor almacenado en SPARE no cambia.
       
        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
        PUSH    HL
                           
        LD      C,4         ; Copiamos los 4 bytes del segundo doble encima de la pila
        LDIR
       
        LD      C,8         ; Movemos los 8 bytes de los dos dobles (ya intercambiados) a la posición original.
        POP     DE 
        LDIR

        JP      (IY)        ; Vuelta a Forth

Esta versión es 2x más rápida que la de Dutra y 4.6x más rápida que la versión Forth.

EDIT:He aplicado la modificación que indicó @jltursan de cambiar el segundo y tercer LD BC,n por LD C,n (dejando el primero para que B=0) y nos ahorramos 2 bytes. El aumento de velocidad, que existe, es muy pequeño (1.4%), por lo que no lo reflejo en el multiplicador.

jltursan
Mensajes: 4879
Registrado: 20 Sep 2011 13:59
Ubicación: Madrid
Agradecido : 739 veces
Agradecimiento recibido: 1644 veces
Contactar:

Re: Jupiter Ace Forth: Utilidades II

Mensajepor jltursan » 17 Sep 2022 15:49

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

Elurdio
Mensajes: 604
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 145 veces
Agradecimiento recibido: 112 veces

Re: Jupiter Ace Forth: Utilidades II

Mensajepor Elurdio » 17 Sep 2022 15:51

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.


Volver a “Jupiter Ace”

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 2 invitados